Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision ca1cfa96ae16130bb07e71808ff2cc4a7792de73)
+++ server/lib/gutenbach/server/job.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -174,5 +174,5 @@
     def is_playing(self):
         """Whether the job is currently playing (regardless of whether
-        it's paused).
+        it's Paused).
 
         """
@@ -190,5 +190,6 @@
         return self.is_valid and \
                self.player is not None and \
-               self.player.is_paused        
+               self.player.paused and \
+               self.player.is_playing
 
     @property
@@ -201,7 +202,10 @@
         return (self.is_valid and \
                 self.player is not None and \
-                self.player.is_done) or \
+                self.player.done) or \
                 (self._why_done == "canceled" or \
                  self._why_done == "aborted")
+
+    def wait_done(self):
+        self.player.wait_done()
 
     @property
Index: server/lib/gutenbach/server/player.py
===================================================================
--- server/lib/gutenbach/server/player.py	(revision d42236e713a7da2a8fc9f482a69e7ff5ab4fc8f5)
+++ server/lib/gutenbach/server/player.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -19,8 +19,9 @@
             self._callback = None
             self._paused = False
+            self._paused_condition = threading.Condition(self.lock)
             self._done = False
+            self._done_condition = threading.Condition(self.lock)
             self._dryrun = False
             self._dryrun_time = 0.5
-            self._lag = 0.01
 
     @property
@@ -30,20 +31,54 @@
             return self.ident is not None and \
                    self.isAlive() and \
-                   not self.is_done
+                   not self.done
         else:
             return self.ident is not None and \
                    self.isAlive() and \
-                   not self.is_done and \
+                   not self.done and \
                    self.player is not None and \
                    self.player.poll() is None
-        
+
+
+    # DONE
     @property
     @sync
-    def is_paused(self):
-        return self.is_playing and self._paused
+    def done(self):
+        return self._done
+    @done.setter
+    @sync
+    def done(self, val):
+        if (self._done != val):
+            self._done = val
+            self._done_condition.notifyAll()
+    @sync
+    def wait_done(self):
+        """Wait for the player to finish playing.
 
+        Requires that the main thread be started.
+        """
+        while not self._done:
+            self._done_condition.wait()
+
+    # PAUSED
     @property
-    def is_done(self):
-        return self._done
+    @sync
+    def paused(self):
+        return self._paused
+    @paused.setter
+    @sync
+    def paused(self, val):
+        if (self._paused != val):
+            self._paused = val
+            self._paused_condition.notifyAll()
+
+    @sync
+    def wait_unpaused(self):
+        """Wait for the player to finish playing.
+
+        Requires that the main thread be started.
+        """
+        while self._paused:
+            self._paused_condition.wait()
+
 
     @property
@@ -57,59 +92,52 @@
     def start(self):
         super(Player, self).start()
-        time.sleep(self._lag)
 
     def run(self):
-        if self.fh is None:
-            raise ValueError, "file handler is None"
-        
-        logger.info("playing file '%s'" % self.fh.name)
+        try:
+            if self.fh is None:
+                raise ValueError, "file handler is None"
 
-        with self.lock:
-            self._paused = False
-            self._done = False
+            logger.info("playing file '%s'" % self.fh.name)
 
-        command = ["mplayer", "-really-quiet", "-slave", self.fh.name]
-        logger.info("running '%s'", " ".join(command))
+            with self.lock:
+                self.paused = False
+                self.done = False
 
-        if self._dryrun:
-            step = 0.01
-            while self._dryrun_time > 0:
-                time.sleep(step)
-                self._dryrun_time -= step
-                while self.is_paused:
-                    time.sleep(0.01)
+            command = ["mplayer", "-really-quiet", "-slave", self.fh.name]
+            logger.info("running '%s'", " ".join(command))
 
-        else:
+            if self._dryrun:
+                step = 0.01
+                while self._dryrun_time > 0:
+                    time.sleep(step)
+                    self._dryrun_time -= step
+                    self.wait_unpaused()
+            else:
+                with self.lock:
+                    self.player = subprocess.Popen(
+                        command,
+                        stdin=subprocess.PIPE,
+                        stderr=subprocess.PIPE,
+                        stdout=subprocess.PIPE)
+
+                # wait for mplayer to finish
+                self.player.wait()
+
+                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)
+        finally:
             with self.lock:
-                self.player = subprocess.Popen(
-                    command,
-                    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)
-
-        with self.lock:
-            if self.callback:
-                self.callback()
-            self._done = True
+                if self.callback:
+                    self.callback()
+                self.done = True
 
     def mplayer_pause(self):
@@ -119,9 +147,8 @@
                 if not self._dryrun:
                     self.player.stdin.write("pause\n")
-                self._paused = not(self._paused)
-                logger.info("paused: %s", self.is_paused)
+                self.paused = not(self.paused)
+                logger.info("paused: %s", self.paused)
             else:
                 logger.warning("trying to pause non-playing job")
-        time.sleep(self._lag)
 
     def mplayer_stop(self):
@@ -133,5 +160,5 @@
                 else:
                     self._dryrun_time = 0.0
-                self._paused = False
+                self.paused = False
                 logger.info("stopped")
             else:
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision 97f20ddea21d7f844430a77b51b0c049f4e9b72e)
+++ server/lib/gutenbach/server/printer.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -11,7 +11,9 @@
 import time
 import traceback
+from queue import PausingQueue
 
 # initialize logger
 logger = logging.getLogger(__name__)
+
 
 class GutenbachPrinter(threading.Thread):
@@ -79,11 +81,10 @@
 
         self.finished_jobs = []
-        self.pending_jobs = []
+        self.lock = threading.RLock()
+        self.pending_jobs = PausingQueue(self.lock)
         self.current_job = None
         self.jobs = {}
 
-        self.lock = threading.RLock()
         self._running = False
-        self.paused = False
 
         # CUPS ignores jobs with id 0, so we have to start at 1
@@ -98,18 +99,31 @@
         return "<Printer '%s'>" % self.name
 
+    @property
+    def paused(self):
+        return self.pending_jobs.paused
+    @paused.setter
+    @sync
+    def paused(self, val):
+        self.pending_jobs.paused = val
+
     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_done:
-                        self.complete_job()
-                except:
-                    self._running = False
-                    logger.fatal(traceback.format_exc())
-                    break
-            time.sleep(0.1)
+        try:
+            while self._running:
+                with self.lock:
+                    try:
+                        self.current_job  = self.get_job(self.pending_jobs.pop())
+                    except IndexError:
+                        self.current_job = None
+                        continue;
+                    self.current_job.play()
+                self.current_job.wait_done()
+                with self.lock:
+                    self.finished_jobs.append(self.current_job.id)
+                    self.current_job = None
+
+        except:
+            self._running = False
+            logger.fatal(traceback.format_exc())
 
     def stop(self):
@@ -123,4 +137,6 @@
                 
             self._running = False
+            self.pending_jobs.interrupt()
+
         if self.ident is not None and self.isAlive():
             self.join()
@@ -179,5 +195,5 @@
     @sync
     def active_jobs(self):
-        jobs = self.pending_jobs[:]
+        jobs = self.pending_jobs.copy()
         if self.current_job is not None:
             jobs.insert(0, self.current_job.id)
@@ -197,26 +213,4 @@
         if not self.is_running:
             raise RuntimeError, "%s not started" % str(self)
-
-    @sync
-    def start_job(self):
-        self.assert_running()
-        if not self.paused and self.current_job is None:
-            try:
-                job_id = self.pending_jobs.pop(0)
-                self.current_job = self.get_job(job_id)
-                self.current_job.play()
-            except IndexError:
-                self.current_job = None
-                    
-    @sync
-    def complete_job(self):
-        self.assert_running()
-        if not self.paused and self.current_job is not None:
-            try:
-                if not self.current_job.is_done:
-                    self.current_job.stop()
-            finally:
-                self.finished_jobs.append(self.current_job.id)
-                self.current_job = None
 
     @sync
@@ -2008,5 +2002,3 @@
         
         self.assert_running()
-        job = self.get_job(job_id)
-        job.priority = 1 # XXX we need to actually do something
-                         # correct here
+        self.pending_jobs.promote(job_id)
Index: server/lib/gutenbach/server/queue.py
===================================================================
--- server/lib/gutenbach/server/queue.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
+++ server/lib/gutenbach/server/queue.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -0,0 +1,93 @@
+import threading
+from . import sync
+
+class InterruptException(Exception):
+    pass
+
+class PausingQueue(object):
+    def __init__(self, lock=None):
+        self._items = []
+        if lock:
+            self.lock = lock
+        else:
+            self.lock = threading.RLock()
+        self._ready = threading.Condition(self.lock)
+        self._interrupted = False
+        self._paused = False
+
+    @sync
+    def interrupt(self):
+        self._interrupted = True
+        self._ready.notifyAll()
+
+    def wait_ready(func):
+        @sync
+        def do(self, *args, **kwargs):
+            while self.paused or not self._items:
+                self._ready.wait()
+                if self._interrupted:
+                    raise InterruptException()
+            return func(self, *args, **kwargs)
+        return do
+
+    def notify_ready(func):
+        @sync
+        def do(self, *args, **kwargs):
+            if not self.paused:
+                ret = func(self, *args, **kwargs)
+                self._ready.notifyAll()
+            return ret
+        return do
+
+    @property
+    @sync
+    def paused(self):
+        return self._paused
+    @paused.setter
+    @sync
+    def paused(self, val):
+        self._paused = val
+        if not self._paused:
+            self._ready.notifyAll()
+
+
+    @wait_ready
+    def pop(self):
+        return self._items.pop(0)
+
+    @notify_ready
+    def push(self, item):
+        self._items.insert(0, item)
+
+    @notify_ready
+    def append(self, item):
+        self._items.append(item)
+
+    @notify_ready
+    def insert(self, index, item):
+        self._items.insert(index, item)
+
+    @sync
+    def promote(self, item):
+        self.move(0, item)
+
+    @sync
+    def move(self, index, item):
+        self._items.remove(item)
+        self._items.insert(index, item)
+
+    @sync
+    def copy(self):
+        return self._items[:]
+
+    @sync
+    def empty(self):
+        return not bool(self._items)
+
+    @sync
+    def __len__(self):
+        return len(self._items)
+
+    @sync
+    def __contains__(self, val):
+        return val in self._items
Index: server/lib/gutenbach_test/server/job.py
===================================================================
--- server/lib/gutenbach_test/server/job.py	(revision 97f20ddea21d7f844430a77b51b0c049f4e9b72e)
+++ server/lib/gutenbach_test/server/job.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -176,6 +176,5 @@
         self.assertFalse(self.job.is_paused)
         
-        while self.job.is_playing:
-            time.sleep(0.1)
+        self.job.wait_done()
             
         self.assertTrue(self.job.is_done)
@@ -225,4 +224,4 @@
 
 if __name__ == "__main__":
-    logging.basicConfig(loglevel=logging.CRITICAL)
+    logging.basicConfig(loglevel=logging.DEBUG)
     unittest.main()
Index: server/lib/gutenbach_test/server/printer.py
===================================================================
--- server/lib/gutenbach_test/server/printer.py	(revision 410ad69bd8f3ec09b9dff88c28c37e2933f37ee0)
+++ server/lib/gutenbach_test/server/printer.py	(revision 3c0760f5a0a3dbb2fadaa810d1e000a87dadde19)
@@ -42,5 +42,5 @@
         self.assertEqual(self.printer.finished_jobs, [])
     def testPendingJobs(self):
-        self.assertEqual(self.printer.pending_jobs, [])
+        self.assertTrue(self.printer.pending_jobs.empty())
     def testCurrentJob(self):
         self.assertEqual(self.printer.current_job, None)
@@ -58,8 +58,4 @@
         self.assertEqual(self.printer.state, States.STOPPED)
 
-    def testStartJob(self):
-        self.assertRaises(RuntimeError, self.printer.start_job)
-    def testCompleteJob(self):
-        self.assertRaises(RuntimeError, self.printer.complete_job)
     def testGetJob(self):
         self.assertRaises(RuntimeError, self.printer.get_job, 0)
