Skip to content
Snippets Groups Projects
  • Martin Poirier's avatar
    395bca8b
    netrender · 395bca8b
    Martin Poirier authored
    job and frame status cleanup (move to the model module)
    record transitions between states in job to be able to get (among others) the started and finished time.
    395bca8b
    History
    netrender
    Martin Poirier authored
    job and frame status cleanup (move to the model module)
    record transitions between states in job to be able to get (among others) the started and finished time.
model.py 14.62 KiB
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import netrender.versioning as versioning
from netrender.utils import *

import time

# Jobs status
JOB_WAITING = 0 # before all data has been entered
JOB_PAUSED = 1 # paused by user
JOB_FINISHED = 2 # finished rendering
JOB_QUEUED = 3 # ready to be dispatched

JOB_STATUS_TEXT = {
        JOB_WAITING: "Waiting",
        JOB_PAUSED: "Paused",
        JOB_FINISHED: "Finished",
        JOB_QUEUED: "Queued"
        }

JOB_TRANSITION_STARTED = "Started"
JOB_TRANSITION_PAUSED = "Paused"
JOB_TRANSITION_RESUMED = "Resumed"
JOB_TRANSITION_FINISHED = "Finished"
JOB_TRANSITION_RESTARTED = "Restarted"

JOB_TRANSITIONS = {
       (JOB_WAITING, JOB_QUEUED) : JOB_TRANSITION_STARTED,
       (JOB_QUEUED, JOB_PAUSED) : JOB_TRANSITION_PAUSED,
       (JOB_PAUSED, JOB_QUEUED) : JOB_TRANSITION_RESUMED,
       (JOB_QUEUED, JOB_FINISHED) : JOB_TRANSITION_FINISHED,
       (JOB_FINISHED, JOB_QUEUED) : JOB_TRANSITION_RESTARTED
       }

# Job types (depends on the dependency type)
JOB_BLENDER = 1
JOB_PROCESS = 2
JOB_VCS     = 3

JOB_TYPES = {
                JOB_BLENDER: "Blender",
                JOB_PROCESS: "Process",
                JOB_VCS:     "Versioned",
            }

JOB_SUB_RENDER = 1
JOB_SUB_BAKING = 2

# Job subtypes
JOB_SUBTYPES = {
                JOB_SUB_RENDER: "Render",
                JOB_SUB_BAKING: "Baking",
            }


# Frames status
FRAME_QUEUED = 0
FRAME_DISPATCHED = 1
FRAME_DONE = 2
FRAME_ERROR = 3

FRAME_STATUS_TEXT = {
        FRAME_QUEUED: "Queued",
        FRAME_DISPATCHED: "Dispatched",
        FRAME_DONE: "Done",
        FRAME_ERROR: "Error"
        }

# Tags
TAG_BAKING = "baking"
TAG_RENDER = "render"

TAG_ALL = set((TAG_BAKING, TAG_RENDER))

class LogFile:
    def __init__(self, job_id = 0, slave_id = 0, frames = []):
        self.job_id = job_id
        self.slave_id = slave_id
        self.frames = frames

    def serialize(self):
        return 	{
                            "job_id": self.job_id,
                            "slave_id": self.slave_id,
                            "frames": self.frames
                        }

    @staticmethod
    def materialize(data):
        if not data:
            return None

        logfile = LogFile()
        logfile.job_id = data["job_id"]
        logfile.slave_id = data["slave_id"]
        logfile.frames = data["frames"]

        return logfile

class RenderSlave:
    _slave_map = {}

    def __init__(self, info = None):
        self.id = ""
        self.total_done = 0
        self.total_error = 0
        self.last_seen = 0.0
        
        if info:
            self.name = info.name
            self.address = info.address
            self.stats = info.stats
            self.tags = info.tags
        else:
            self.name = ""
            self.address = ("",0)
            self.stats = ""
            self.tags = set()

    def serialize(self):
        return 	{
                            "id": self.id,
                            "name": self.name,
                            "address": self.address,
                            "stats": self.stats,
                            "total_done": self.total_done,
                            "total_error": self.total_error,
                            "last_seen": self.last_seen,
                            "tags": tuple(self.tags)
                        }

    @staticmethod
    def materialize(data, cache = True):
        if not data:
            return None

        slave_id = data["id"]

        if cache and slave_id in RenderSlave._slave_map:
            return RenderSlave._slave_map[slave_id]

        slave = RenderSlave()
        slave.id = slave_id
        slave.name = data["name"]
        slave.address = data["address"]
        slave.stats = data["stats"]
        slave.total_done = data["total_done"]
        slave.total_error = data["total_error"]
        slave.last_seen = data["last_seen"]
        slave.tags = set(data["tags"])

        if cache:
            RenderSlave._slave_map[slave_id] = slave

        return slave

class VersioningInfo:
    def __init__(self, info = None):
        self._system = None
        self.wpath = ""
        self.rpath = ""
        self.revision = ""
        
    @property
    def system(self):
        return self._system

    @system.setter
    def system(self, value):
        self._system = versioning.SYSTEMS[value]

    def update(self):
        self.system.update(self)
    
    def serialize(self):
        return {
                "wpath": self.wpath,
                "rpath": self.rpath,
                "revision": self.revision,
                "system": self.system.name
                }
        
    @staticmethod
    def generate(system, path):
        vs = VersioningInfo()
        vs.wpath = path
        vs.system = system

        vs.rpath = vs.system.path(path)
        vs.revision = vs.system.revision(path)
        
        return vs
        
        
    @staticmethod
    def materialize(data):
        if not data:
            return None
        
        vs = VersioningInfo()
        vs.wpath = data["wpath"]
        vs.rpath = data["rpath"]
        vs.revision = data["revision"]
        vs.system = data["system"]
        
        return vs
        

class RenderFile:
    def __init__(self, filepath = "", index = 0, start = -1, end = -1, signature = 0):
        self.filepath = filepath
        self.original_path = filepath
        self.signature = signature
        self.index = index
        self.start = start
        self.end = end
        self.force = False
        

    def serialize(self):
        return 	{
                    "filepath": self.filepath,
                    "original_path": self.original_path,
                    "index": self.index,
                    "start": self.start,
                    "end": self.end,
                    "signature": self.signature,
                    "force": self.force
                    
                }

    @staticmethod
    def materialize(data):
        if not data:
            return None

        rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"], data["signature"])
        rfile.original_path = data["original_path"]
        rfile.force = data["force"]

        return rfile

class RenderJob:
    def __init__(self, info = None):
        self.id = ""
        
        self.resolution = None

        self.usage = 0.0
        self.last_dispatched = 0.0
        self.frames = []
        self.transitions = []
        
        self._status = None
        
        if info:
            self.type = info.type
            self.subtype = info.subtype
            self.name = info.name
            self.category = info.category
            self.tags = info.tags
            self.status = info.status
            self.files = info.files
            self.chunks = info.chunks
            self.priority = info.priority
            self.blacklist = info.blacklist
            self.version_info = info.version_info
            self.render = info.render
        else:
            self.type = JOB_BLENDER
            self.subtype = JOB_SUB_RENDER
            self.name = ""
            self.category = "None"
            self.tags = set()
            self.status = JOB_WAITING
            self.files = []
            self.chunks = 0
            self.priority = 0
            self.blacklist = []
            self.version_info = None
            self.render = "BLENDER_RENDER"

    @property
    def status(self):
        """Status of the job (waiting, paused, finished or queued)"""
        return self._status
    
    @status.setter
    def status(self, value):
        transition = JOB_TRANSITIONS.get((self.status, value), None)
        if transition:
            self.transitions.append((transition, time.time()))
            
        self._status = value

    @property
    def time_started(self):
        started_time = None
        for transition, time_value in self.transitions:
            if transition == JOB_TRANSITION_STARTED:
                started_time = time_value
                break
            
        return started_time

    @property
    def time_finished(self):
        finished_time = None
        if self.status == JOB_FINISHED:
            for transition, time_value in self.transitions:
                if transition == JOB_TRANSITION_FINISHED:
                    finished_time = time_value
            
        return finished_time

    def hasRenderResult(self):
        return self.subtype == JOB_SUB_RENDER

    def rendersWithBlender(self):
        return self.subtype == JOB_SUB_RENDER

    def addFile(self, file_path, start=-1, end=-1, signed=True):
        def isFileInFrames():
            if start == end == -1:
                return True
            
            for rframe in self.frames:
                if start <= rframe.number<= end:
                    return True
            
            return False
            
            
        if isFileInFrames(): 
            if signed:
                signature = hashFile(file_path)
            else:
                signature = None
            self.files.append(RenderFile(file_path, len(self.files), start, end, signature))

    def addFrame(self, frame_number, command = ""):
        frame = RenderFrame(frame_number, command)
        self.frames.append(frame)
        return frame

    def __len__(self):
        return len(self.frames)

    def countFrames(self, status=FRAME_QUEUED):
        total = 0
        for f in self.frames:
            if f.status == status:
                total += 1

        return total

    def countSlaves(self):
        return len(set((frame.slave for frame in self.frames if frame.status == FRAME_DISPATCHED)))

    def statusText(self):
        return JOB_STATUS_TEXT[self.status]

    def framesStatus(self):
        results = {
                                FRAME_QUEUED: 0,
                                FRAME_DISPATCHED: 0,
                                FRAME_DONE: 0,
                                FRAME_ERROR: 0
                            }

        for frame in self.frames:
            results[frame.status] += 1

        return results

    def __contains__(self, frame_number):
        for f in self.frames:
            if f.number == frame_number:
                return True
        else:
            return False

    def __getitem__(self, frame_number):
        for f in self.frames:
            if f.number == frame_number:
                return f
        else:
            return None

    def serialize(self, frames = None,withFiles=True,withFrames=True):
        min_frame = min((f.number for f in frames)) if frames else -1
        max_frame = max((f.number for f in frames)) if frames else -1
        data={
                            "id": self.id,
                            "type": self.type,
                            "subtype": self.subtype,
                            "name": self.name,
                            "category": self.category,
                            "tags": tuple(self.tags),
                            "status": self.status,
                            "transitions": self.transitions,
                            "chunks": self.chunks,
                            "priority": self.priority,
                            "usage": self.usage,
                            "blacklist": self.blacklist,
                            "last_dispatched": self.last_dispatched,
                            "version_info": self.version_info.serialize() if self.version_info else None,
                            "resolution": self.resolution,
                            "render": self.render
                        }
        if (withFiles):
           data["files"]=[f.serialize() for f in self.files if f.start == -1 or not frames or (f.start <= max_frame and f.end >= min_frame)]
          
        if (withFrames):
           data["frames"]=[f.serialize() for f in self.frames if not frames or f in frames]
           
        return data
    @staticmethod
    def materialize(data):
        if not data:
            return None

        job = RenderJob()
        job.id = data["id"]
        job.type = data["type"]
        job.subtype = data["subtype"]
        job.name = data["name"]
        job.category = data["category"]
        job.tags = set(data["tags"])
        job.status = data["status"]
        job.transitions = data["transitions"]
        job.files = [RenderFile.materialize(f) for f in data["files"]]
        job.frames = [RenderFrame.materialize(f) for f in data["frames"]]
        job.chunks = data["chunks"]
        job.priority = data["priority"]
        job.usage = data["usage"]
        job.blacklist = data["blacklist"]
        job.last_dispatched = data["last_dispatched"]
        job.resolution = data["resolution"]
        job.render=data["render"]
        
        version_info = data.get("version_info", None)
        if version_info:
            job.version_info = VersioningInfo.materialize(version_info)

        return job

class RenderFrame:
    def __init__(self, number = 0, command = ""):
        self.number = number
        self.time = 0
        self.status = FRAME_QUEUED
        self.slave = None
        self.command = command
        self.results = []   # List of filename of result files associated with this frame

    def statusText(self):
        return FRAME_STATUS_TEXT[self.status]

    def serialize(self):
        return 	{
                            "number": self.number,
                            "time": self.time,
                            "status": self.status,
                            "slave": None if not self.slave else self.slave.serialize(),
                            "command": self.command,
                            "results": self.results
                        }

    @staticmethod
    def materialize(data):
        if not data:
            return None

        frame = RenderFrame()
        frame.number = data["number"]
        frame.time = data["time"]
        frame.status = data["status"]
        frame.slave = RenderSlave.materialize(data["slave"])
        frame.command = data["command"]
        frame.results = data["results"]

        return frame