Index: server/lib/gutenbach/server/__init__.py
===================================================================
--- server/lib/gutenbach/server/__init__.py	(revision e58af053711ea43159e19ceeb7875364174b2b1f)
+++ server/lib/gutenbach/server/__init__.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -14,6 +14,5 @@
 __all__.append('GutenbachRequestHandler')
 
-from server import GutenbachServer, IPPServer
-__all__.append('GutenbachServer')
+from server import IPPServer
 __all__.append('IPPServer')
 
@@ -24,15 +23,15 @@
 
 # configure and initialize logging
-logging.basicConfig(level=logging.DEBUG)
+logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
 def error(self, request=None, client_address=None):
     logger.fatal(traceback.format_exc())
-    self.gutenbach_server.request_stop = True
+    self.gutenbach_printer.running = False
     sys.exit(1)
 
 def start():
     logger.info("Starting Gutenbach server...")
-    gutenbach = GutenbachServer()
+    gutenbach = GutenbachPrinter("test")
     gutenbach.start()
 
@@ -41,5 +40,5 @@
     httpd = BaseHTTPServer.HTTPServer(server_address, IPPServer)
     httpd.handle_error = error.__get__(httpd)
-    httpd.gutenbach_server = gutenbach
+    httpd.gutenbach_printer = gutenbach
     while gutenbach.isAlive():
         try:
Index: server/lib/gutenbach/server/errors.py
===================================================================
--- server/lib/gutenbach/server/errors.py	(revision b01b6d11a9f096abe2a8f080b7ea0aeb9b50edbb)
+++ server/lib/gutenbach/server/errors.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -3,4 +3,5 @@
     'InvalidPrinterStateException',
     'InvalidJobStateException',
+    'MissingDataException'
     ]
 
@@ -18,6 +19,20 @@
 
 class InvalidJobStateException(Exception):
+    errstr = {
+        3: "pending",
+        4: "held",
+        5: "processing",
+        6: "stopped",
+        7: "cancelled",
+        8: "aborted",
+        9: "complete"
+        }
+    
     def __init__(self, state):
-        self.state = hex(state)
+        self.state = int(state)
     def __str__(self):
-        return "Invalid job state: %s" % self.state
+        return "Invalid job state: %s (%s)" % \
+               (self.errstr[self.state], hex(self.state))
+
+class MissingDataException(Exception):
+    pass
Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision be6ff03e6c210100e1b00f790c0cfa25b8fecc87)
+++ server/lib/gutenbach/server/job.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -1,9 +1,8 @@
-from . import InvalidJobException, InvalidPrinterStateException
+from . import InvalidJobStateException, MissingDataException
+from .player import Player
 from gutenbach.ipp import JobStates as States
 import os
 import gutenbach.ipp as ipp
 import logging
-import subprocess
-import time
 
 # initialize logger
@@ -34,7 +33,7 @@
         self.name = name
         self.size = size
-	self.status = States.HELD
-
-        self.document = None
+	self.state = States.HELD
+        self.priority = 1
+
         self.document_name = None
         self.document_format = None
@@ -48,4 +47,7 @@
         return "<Job %d '%s'>" % (self.id, self.name)
 
+    def __cmp__(self, other):
+        return cmp(self.priority, other.priority)
+
     ######################################################################
     ###                          Properties                            ###
@@ -98,9 +100,5 @@
 
         """
-        if self.document:
-            size = os.path.getsize(self.document.name)
-        else:
-            size = self._size
-        return size
+        return self._size
     @size.setter
     def size(self, val):
@@ -110,4 +108,16 @@
             self._size = 0
 
+    @property
+    def is_playing(self):
+        return self.state == States.PROCESSING
+
+    @property
+    def is_ready(self):
+        return self.state == States.PENDING
+
+    @property
+    def is_finished(self):
+        return self.state != States.PENDING and self.state != States.PROCESSING
+        
     ######################################################################
     ###                            Methods                             ###
@@ -115,25 +125,55 @@
 
     def play(self):
+        """Non-blocking play function.
+
+        """
+        
+        # make sure the job is waiting to be played and that it's
+        # valid
+        if self.state != States.PENDING:
+            raise InvalidJobStateException(self.state)
+        
+        # and set the state to processing if we're good to go
         logger.info("playing job %s" % str(self))
-	self.status = States.PROCESSING
-        self.player = subprocess.Popen(
-            "/usr/bin/mplayer -really-quiet -slave %s" % self.document.name,
-            shell=True,
-            stderr=subprocess.PIPE,
-            stdout=subprocess.PIPE)
-        while self.player.poll() is None:
-            time.sleep(0.1)
-        logger.info("mplayer finished with code %d" % self.player.returncode)
-        stderr = self.player.stderr.read()
-        stdout = self.player.stdout.read()
-        if stderr.strip() != "":
-            logger.error(stderr)
-        logger.debug(stdout)
-        self.player = None
-	self.printer.complete_job(self.id)
-
-    def finish(self):
-        logger.info("finished job %s" % str(self))
-	self.status = States.COMPLETE
+	self.state = States.PROCESSING
+        self.player.callback = self._completed
+        self.player.run()
+
+    def pause(self):
+        if self.player:
+            self.player.mplayer_pause()
+
+    def stop(self):
+        if self.player:
+            self.player.callback = self._stopped
+            self.player.mplayer_stop()
+
+    def _completed(self):
+        if self.state != States.PROCESSING:
+            raise InvalidJobStateException(self.state)
+        logger.info("completed job %s" % str(self))
+	self.state = States.COMPLETE
+        self.player = None
+
+    def _canceled(self):
+        if self.state != States.PROCESSING:
+            raise InvalidJobStateException(self.state)
+        logger.info("canceled job %s" % str(self))
+        self.state = States.CANCELLED
+        self.player = None
+
+    def _stopped(self):
+        if self.state != States.PROCESSING:
+            raise InvalidJobStateException(self.state)
+        logger.info("stopped job %s" % str(self))
+        self.state = States.STOPPED
+        self.player = None
+
+    def _aborted(self):
+        if self.state != States.PROCESSING:
+            raise InvalidJobStateException(self.state)
+        logger.info("aborted job %s" % str(self))
+        self.state = States.ABORTED
+        self.player = None
 
     ######################################################################
@@ -149,10 +189,8 @@
         return ipp.JobName(self.name)
 
-    # XXX: we need to actually calculate this!
     @property
     def job_originating_user_name(self):
         return ipp.JobOriginatingUserName(self.creator)
 
-    # XXX: we need to actually calculate this!
     @property
     def job_k_octets(self):
@@ -161,5 +199,5 @@
     @property
     def job_state(self):
-        return ipp.JobState(self.status)
+        return ipp.JobState(self.state)
 
     @property
@@ -175,17 +213,17 @@
         pass
 
-    def send_document(self,
-                      document,
-                      document_name=None,
-                      document_format=None,
-                      document_natural_language=None,
-                      requesting_user_name=None,
-                      compression=None,
+    def send_document(self, document, document_name=None,
+                      document_format=None, document_natural_language=None,
+                      requesting_user_name=None, compression=None,
                       last_document=None):
 
-        if self.status != States.HELD:
-            raise InvalidJobStateException(self.status)
-        
-        self.document = document
+        if self.state != States.HELD:
+            raise InvalidJobStateException(self.state)
+
+        self.player = Player(document)
+
+        if self.size == 0:
+            self.size = os.path.getsize(document.name)
+        
         self.document_name = str(document_name)
         self.document_format = str(document_format)
@@ -193,5 +231,5 @@
         self.creator = str(requesting_user_name)
         self.compression = str(compression)
-        self.status = States.PENDING
+        self.state = States.PENDING
 
         logger.debug("document for job %d is '%s'" % (self.id, self.document_name))
Index: server/lib/gutenbach/server/player.py
===================================================================
--- server/lib/gutenbach/server/player.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
+++ server/lib/gutenbach/server/player.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -0,0 +1,83 @@
+import logging
+import threading
+import subprocess
+import time
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+class Player(threading.Thread):
+
+    def __init__(self, fh, *args, **kwargs):
+        self.lock = threading.RLock()
+
+        with self.lock:
+            super(Player, self).__init__(*args, **kwargs)
+            self.fh = fh
+            self.player = None
+            self._callback = None
+
+    @property
+    def is_playing(self):
+        with self.lock:
+            playing = self.player and self.player.poll() is None
+        return playing
+
+    @property
+    def callback(self):
+        return self._callback
+    @callback.setter
+    def callback(self, val):
+        with self.lock:
+            self._callback = val
+
+    def run(self):
+        if self.fh is None:
+            raise ValueError, "file handler is None"
+        
+        self.mplayer_play()
+        with self.lock:
+            if self.callback:
+                self.callback()
+
+    def mplayer_play(self):
+        logger.info("playing file '%s'" % self.fh.name)
+        
+        # open mplayer
+        with self.lock:
+            self.player = subprocess.Popen(
+                "/usr/bin/mplayer -really-quiet -slave %s" % self.fh.name,
+                shell=True,
+                stdin=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                stdout=subprocess.PIPE)
+
+        # wait for mplayer to finish
+        while True:
+            with self.lock:
+                playing = self.is_playing
+            if not playing:
+                break
+            time.sleep(0.1)
+            
+        logger.info("mplayer finished with code %d" % self.player.returncode)
+
+        # get output from mplayer and log it
+        with self.lock:
+            stderr = self.player.stderr.read()
+            stdout = self.player.stdout.read()
+            
+        if stderr.strip() != "":
+            logger.error(stderr)
+        if stdout.strip() != "":
+            logger.debug(stdout)
+
+    def mplayer_pause(self):
+        with self.lock:
+            if self.is_playing:
+                self.player.stdin.write("pause\n")
+                
+    def mplayer_stop(self):
+        with self.lock:
+            if self.is_playing:
+                self.player.stdin.write("quit\n")
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision b01b6d11a9f096abe2a8f080b7ea0aeb9b50edbb)
+++ server/lib/gutenbach/server/printer.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -1,3 +1,3 @@
-from . import InvalidJobException, InvalidPrinterStateException
+from . import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
 from . import Job
 from gutenbach.ipp import PrinterStates as States
@@ -5,9 +5,14 @@
 import logging
 import time
+import threading
+import heapq
+import traceback
+import sys
+
 
 # initialize logger
 logger = logging.getLogger(__name__)
 
-class GutenbachPrinter(object):
+class GutenbachPrinter(threading.Thread):
 
     # for IPP
@@ -44,16 +49,23 @@
     ]
         
-    def __init__(self, name):
-
-	self.name = name
-        self.time_created = int(time.time())
-        self.state = States.IDLE
-
-	self.finished_jobs = []
-	self.active_jobs = []
-	self.jobs = {}
-
-        # cups ignores jobs with id 0, so we have to start at 1
-	self._next_jobid = 1
+    def __init__(self, name, *args, **kwargs):
+        self.lock = threading.RLock()
+
+        with self.lock:
+            super(GutenbachPrinter, self).__init__(*args, **kwargs)
+            
+            self.name = name
+            self.time_created = int(time.time())
+
+            self.finished_jobs = []
+            self.pending_jobs = []
+            self.current_job = None
+            self.jobs = {}
+
+            self.running = False
+            self.paused = False
+
+            # CUPS ignores jobs with id 0, so we have to start at 1
+            self._next_job_id = 1
 
     def __repr__(self):
@@ -78,39 +90,75 @@
 
     @property
-    def next_job(self):
-        if len(self.active_jobs) == 0:
-            job = None
-        else:
-            job = self.active_jobs[0]
+    def state(self):
+        with self.lock:
+            if self.current_job is not None:
+                val = States.PROCESSING
+            elif len(self.pending_jobs) == 0:
+                val = States.IDLE
+            else:
+                val = States.STOPPED
+        return val
+
+    @property
+    def active_jobs(self):
+        with self.lock:
+            jobs = self.pending_jobs[:]
+            if self.current_job is not None:
+                jobs.insert(0, self.current_job.id)
+        return jobs
+
+    ######################################################################
+    ###                            Methods                             ###
+    ######################################################################
+
+    def run(self):
+        self.running = True
+        while self.running:
+            with self.lock:
+                try:
+                    if self.current_job is None:
+                        self.start_job()
+                    elif self.current_job.is_finished:
+                        self.complete_job()
+                except:
+                    logger.fatal(traceback.format_exc())
+                    sys.exit(1)
+            time.sleep(0.1)
+
+    def start_job(self):
+        with self.lock:
+            if self.current_job is None:
+                try:
+                    job_id = heapq.heappop(self.pending_jobs)
+                    self.current_job = self.get_job(job_id)
+                    self.current_job.play()
+                except IndexError:
+                    pass
+                except InvalidJobStateException:
+                    heapq.heappush(self.pending_jobs, self.current_job.id)
+                finally:
+                    self.current_job = None
+                    
+    def complete_job(self):
+        with self.lock:
+            if self.current_job is None:
+                return
+
+            try:
+                if not self.current_job.is_finished:
+                    self.current_job.stop()
+            finally:
+                self.finished_jobs.append(self.current_job)
+                self.current_job = None
+
+    def stop(self):
+        pass
+
+    def get_job(self, job_id):
+        with self.lock:
+            if job_id not in self.jobs:
+                raise InvalidJobException(job_id)
+            job = self.jobs[job_id]
         return job
-
-    ######################################################################
-    ###                            Methods                             ###
-    ######################################################################
-
-    def complete_job(self, jobid):
-	job = self.jobs[self.active_jobs.pop(0)]
-	self.finished_jobs.append(job)
-	job.finish()
-	return job.id
-
-    def start_job(self, jobid):
-	job = self.jobs[self.active_jobs[0]]
-	if job.status != ipp.JobStates.PENDING:
-	    raise InvalidPrinterStateException(job.status)
-	job.play()
-
-    def stop(self):
-        if len(self.active_jobs) == 0:
-            return
-        job = self.jobs[self.active_jobs[0]]
-        if job.player is not None:
-            logger.info("stopping printer %s" % self.name)
-            job.player.terminate()
-
-    def get_job(self, jobid):
-	if jobid not in self.jobs:
-	    raise InvalidJobException(jobid)
-	return self.jobs[jobid]
 
     ######################################################################
@@ -238,6 +286,6 @@
 
     def create_job(self, requesting_user_name="", job_name="", job_k_octets=0):
-        job_id = self._next_jobid
-        self._next_jobid += 1
+        job_id = self._next_job_id
+        self._next_job_id += 1
         
         job = Job(job_id,
@@ -248,6 +296,5 @@
         
         self.jobs[job_id] = job
-        self.active_jobs.append(job_id)
-        self.state = States.PROCESSING
+        self.pending_jobs.append(job_id)
         
         return job
Index: server/lib/gutenbach/server/requests.py
===================================================================
--- server/lib/gutenbach/server/requests.py	(revision b01b6d11a9f096abe2a8f080b7ea0aeb9b50edbb)
+++ server/lib/gutenbach/server/requests.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -47,7 +47,6 @@
 class GutenbachRequestHandler(object):
 
-    def __init__(self, gutenbach_server):
-        self.gutenbach_server = gutenbach_server
-        self.printer = gutenbach_server.printer
+    def __init__(self, gutenbach_printer):
+        self.printer = gutenbach_printer
 
     def generic_handle(self, request):
Index: server/lib/gutenbach/server/server.py
===================================================================
--- server/lib/gutenbach/server/server.py	(revision ffbe41dc14056c0f5a55064f93b535e455ba5f70)
+++ server/lib/gutenbach/server/server.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
@@ -8,29 +8,8 @@
 import logging
 import sys
-import traceback
 import tempfile
-import threading
-import time
 
 # initialize logger
 logger = logging.getLogger(__name__)
-
-class GutenbachServer(threading.Thread):
-
-    def run(self):
-        self.printer = GutenbachPrinter(name="test")
-        self.request_stop = False
-
-        while not self.request_stop:
-            job = self.printer.next_job
-            if job is not None:
-                try:
-                    self.printer.start_job(job)
-                except InvalidPrinterStateException:
-                    pass
-                except:
-                    logger.fatal(traceback.format_exc())
-                    sys.exit(1)
-            time.sleep(0.1)
 
 class IPPServer(BaseHTTPServer.BaseHTTPRequestHandler):
@@ -107,4 +86,4 @@
         # throw a fatal error.
         logger.debug("request: %s" % repr(request))
-        response = GutenbachRequestHandler(self.server.gutenbach_server).handle(request)
+        response = GutenbachRequestHandler(self.server.gutenbach_printer).handle(request)
         self.send_ok(response)
