Index: server/lib/gutenbach/server/errors.py
===================================================================
--- server/lib/gutenbach/server/errors.py	(revision 951ab1bdfe4dd0ee569e0d9fdad9d257eac7623b)
+++ server/lib/gutenbach/server/errors.py	(revision 5e70cc289dc0623361caa4e14fcde82a21248f4c)
@@ -13,8 +13,15 @@
 
 class InvalidPrinterStateException(Exception):
+    errstr = {
+        3: "idle",
+        4: "processing",
+        5: "stopped"
+        }
+    
     def __init__(self, state):
-        self.state = hex(state)
+        self.state = int(state)
     def __str__(self):
-        return "Invalid printer state: %s" % self.state
+        return "Invalid printer state: %s (%s)" % \
+               (self.errstr[self.state], hex(self.state))
 
 class InvalidJobStateException(Exception):
Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision bd5bffcd3ecf6e7bb5aefc5caae54483392c5f06)
+++ server/lib/gutenbach/server/job.py	(revision 0494098be71273aa720ffe002e05a27ae68284e3)
@@ -415,6 +415,11 @@
             raise errors.InvalidJobStateException(self.state)
 
-        logger.debug("restarting job %d" % (self.id, self.document))
+        logger.debug("restarting job %d", self.id)
 
         self._why_done = None
-        self.spool(self.document)
+        fh = self.player.fh
+
+        if not fh or fh.closed:
+            raise RuntimeError, "file handler is closed"
+
+        self.player = Player(fh)
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision 57bc2dca8c24215f23d2db575746bec4b66fc245)
+++ server/lib/gutenbach/server/printer.py	(revision 7e29e6ad87196daa2784b1b954c3d7ae2d08970b)
@@ -85,5 +85,5 @@
 
         self.lock = threading.RLock()
-        self.running = False
+        self._running = False
         self.paused = False
 
@@ -91,27 +91,58 @@
         self._next_job_id = 1
 
+    @sync
     def __repr__(self):
         return str(self)
 
+    @sync
     def __str__(self):
-        return "<Printer '%s'>" % self.name 
+        return "<Printer '%s'>" % self.name
 
     def run(self):
-        self.running = True
-        while self.running:
-            with self.lock:
-                try:
-                    if not self.paused and self.current_job is None:
+        self._running = True
+        while self._running:
+            try:
+                with self.lock:
+                    if self.current_job is None:
                         self.start_job()
                     elif self.current_job.is_done:
                         self.complete_job()
-                except:
-                    logger.fatal(traceback.format_exc())
-                    sys.exit(1)
+            except:
+                logger.fatal(traceback.format_exc())
+                sys.exit(1)
             time.sleep(0.1)
 
+    def stop(self):
+        with self.lock:
+            self._running = False
+        if self.ident is not None and self.isAlive():
+            self.join()
+
     ######################################################################
     ###                          Properties                            ###
     ######################################################################
+
+    @property
+    def name(self):
+        return self._name
+    @name.setter
+    def name(self, val):
+        try:
+            self._name = str(val)
+        except:
+            self._name = "gutenbach-printer"
+
+    @property
+    def config(self):
+        return self._config
+    @config.setter
+    def config(self, val):
+        try:
+            _config = dict(val).copy()
+        except:
+            raise ValueError, "not a dictionary"
+        if 'ipp-versions' not in _config:
+            raise ValueError, "missing ipp-versions"
+        self._config = _config
 
     @property
@@ -128,10 +159,11 @@
     @sync
     def state(self):
-        if self.current_job is not None:
-            return States.PROCESSING
-        elif len(self.pending_jobs) == 0:
-            return States.IDLE
+        if self.is_running and self.current_job is not None:
+            state = States.PROCESSING
+        elif self.is_running and len(self.pending_jobs) == 0:
+            state = States.IDLE
         else:
-            return States.STOPPED
+            state = States.STOPPED
+        return state
 
     @property
@@ -143,4 +175,9 @@
         return jobs
 
+    @property
+    def is_running(self):
+        running = self.ident is not None and self.isAlive() and self._running
+        return running
+
     ######################################################################
     ###                            Methods                             ###
@@ -148,6 +185,12 @@
 
     @sync
+    def assert_running(self):
+        if not self.is_running:
+            raise RuntimeError, "%s not started" % str(self)
+
+    @sync
     def start_job(self):
-        if self.current_job is None:
+        self.assert_running()
+        if not self.paused and self.current_job is None:
             try:
                 job_id = heapq.heappop(self.pending_jobs)
@@ -162,16 +205,16 @@
     @sync
     def complete_job(self):
-        if self.current_job is None:
-            return
-
-        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
+        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
     def get_job(self, job_id):
+        self.assert_running()
         if job_id not in self.jobs:
             raise InvalidJobException(job_id)
@@ -184,49 +227,63 @@
     @property
     def printer_uri_supported(self):
+        self.assert_running()
         return ipp.PrinterUriSupported(self.uri)
     @printer_uri_supported.setter
     def printer_uri_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-uri-supported")
 
     @property
     def uri_authentication_supported(self):
+        self.assert_running()
         return ipp.UriAuthenticationSupported("none")
     @uri_authentication_supported.setter
     def uri_authentication_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
 
     @property
     def uri_security_supported(self):
+        self.assert_running()
         return ipp.UriSecuritySupported("none")
     @uri_security_supported.setter
     def uri_security_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("uri-security-supported")
 
     @property
     def printer_name(self):
+        self.assert_running()
         return ipp.PrinterName(self.name)
     @printer_name.setter
     def printer_name(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-name")
 
     @property
     def printer_state(self):
+        self.assert_running()
         return ipp.PrinterState(self.state)
     @printer_state.setter
     def printer_state(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-state")
 
     @property
     def printer_state_reasons(self):
+        self.assert_running()
         return ipp.PrinterStateReasons("none")
     @printer_state_reasons.setter
     def printer_state_reasons(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-state-reasons")
 
     @property
     def ipp_versions_supported(self):
+        self.assert_running()
         return ipp.IppVersionsSupported(*self.config['ipp-versions'])
     @ipp_versions_supported.setter
     def ipp_versions_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
 
@@ -234,98 +291,126 @@
     @property
     def operations_supported(self):
+        self.assert_running()
         return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
     @operations_supported.setter
     def operations_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("operations-supported")
 
     @property
     def charset_configured(self):
+        self.assert_running()
         return ipp.CharsetConfigured("utf-8") # XXX
     @charset_configured.setter
     def charset_configured(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("charset-configured")
         
     @property
     def charset_supported(self):
+        self.assert_running()
         return ipp.CharsetSupported("utf-8") # XXX
     @charset_supported.setter
     def charset_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("charset-supported")
 
     @property
     def natural_language_configured(self):
+        self.assert_running()
         return ipp.NaturalLanguageConfigured("en-us")
     @natural_language_configured.setter
     def natural_language_configured(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("natural-language-configured")
 
     @property
     def generated_natural_language_supported(self):
+        self.assert_running()
         return ipp.GeneratedNaturalLanguageSupported("en-us")
     @generated_natural_language_supported.setter
     def generated_natural_language_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
 
     @property
     def document_format_default(self):
+        self.assert_running()
         return ipp.DocumentFormatDefault("application/octet-stream")
     @document_format_default.setter
     def document_format_default(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("document-format-default")
 
     @property
     def document_format_supported(self):
+        self.assert_running()
         return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
     @document_format_supported.setter
     def document_format_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("document-format-supported")
 
     @property
     def printer_is_accepting_jobs(self):
+        self.assert_running()
         return ipp.PrinterIsAcceptingJobs(True)
     @printer_is_accepting_jobs.setter
     def printer_is_accepting_jobs(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
 
     @property
     def queued_job_count(self):
+        self.assert_running()
         return ipp.QueuedJobCount(len(self.active_jobs))
     @queued_job_count.setter
     def queued_job_count(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("queued-job-count")
 
     @property
     def pdl_override_supported(self):
+        self.assert_running()
         return ipp.PdlOverrideSupported("not-attempted")
     @pdl_override_supported.setter
     def pdl_override_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("pdl-override-supported")
 
     @property
     def printer_up_time(self):
+        self.assert_running()
         return ipp.PrinterUpTime(int(time.time()) - self.time_created)
     @printer_up_time.setter
     def printer_up_time(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("printer-up-time")
 
     @property
     def compression_supported(self):
+        self.assert_running()
         return ipp.CompressionSupported("none")
     @compression_supported.setter
     def compression_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("compression-supported")
 
     @property
     def multiple_operation_time_out(self):
+        self.assert_running()
         return ipp.MultipleOperationTimeOut(240)
     @multiple_operation_time_out.setter
     def multiple_operation_time_out(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
 
     @property
     def multiple_document_jobs_supported(self):
+        self.assert_running()
         return ipp.MultipleDocumentJobsSupported(False)
     @multiple_document_jobs_supported.setter
     def multiple_document_jobs_supported(self, val):
+        self.assert_running()
         raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
 
@@ -335,24 +420,30 @@
 
     def job_id(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobId(job.id)
 
     def job_name(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobName(job.name)
 
     def job_originating_user_name(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobOriginatingUserName(job.creator)
 
     def job_k_octets(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobKOctets(job.size)
 
     def job_state(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobState(job.state)
 
     def job_printer_uri(self, job_id):
+        self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobPrinterUri(self.uri)
@@ -362,7 +453,10 @@
     ######################################################################
 
+    @sync
     def print_job(self, document, document_name=None, document_format=None,
                   document_natural_language=None, requesting_user_name=None,
                   compression=None, job_name=None, job_k_octets=None):
+
+        self.assert_running()
 
         # create the job
@@ -385,7 +479,10 @@
         return job_id
 
-    def verify_job(self, document_name=None, document_format=None,
-                  document_natural_language=None, requesting_user_name=None,
-                  compression=None, job_name=None, job_k_octets=None):
+    @sync
+    def validate_job(self, document_name=None, document_format=None,
+                     document_natural_language=None, requesting_user_name=None,
+                     compression=None, job_name=None, job_k_octets=None):
+
+        self.assert_running()
 
         job_id = self._next_job_id
@@ -398,7 +495,10 @@
         del job
 
+    @sync
     def get_jobs(self, requesting_user_name=None, which_jobs=None,
                  requested_attributes=None):
         
+        self.assert_running()
+
         # Filter by the which-jobs attribute
         if which_jobs is None:
@@ -425,9 +525,13 @@
         return job_attrs
 
+    @sync
     def print_uri(self):
-        pass
-
+        self.assert_running()
+
+    @sync
     def create_job(self, requesting_user_name=None, job_name=None,
                    job_k_octets=None):
+
+        self.assert_running()
 
         job_id = self._next_job_id
@@ -450,11 +554,11 @@
         Does nothing if the printer is already paused.
         """
-        if self.paused:
-            return
-
-        if self.current_job is not None and self.current_job.is_playing:
-            self.current_job.pause()
-
-        self.paused = True
+        
+        self.assert_running()
+        if not self.paused:
+            if self.current_job is not None and self.current_job.is_playing:
+                self.current_job.pause()
+            self.paused = True
+            logger.info("%s paused", str(self))
 
     @sync
@@ -464,14 +568,15 @@
         Does nothing if the printer is not paused.
         """
-        if not self.paused:
-            return
-
-        if self.current_job is not None:
-            self.current_job.resume()
-
-        self.paused = False
+        
+        self.assert_running()
+        if self.paused:
+            if self.current_job is not None:
+                self.current_job.resume()
+            self.paused = False
+            logger.info("%s unpaused", str(self))
 
     @sync
     def get_printer_attributes(self, requested_attributes=None):
+        self.assert_running()
         if requested_attributes is None:
             requested = self.printer_attributes
@@ -485,5 +590,6 @@
 
     @sync
-    def set_printer_attributes(self, job_id, attributes):
+    def set_printer_attributes(self, attributes):
+        self.assert_running()
         for attr in attributes:
             try:
@@ -494,4 +600,5 @@
     @sync
     def cancel_job(self, job_id, requesting_user_name=None):
+        self.assert_running()
         job = self.get_job(job_id)
         try:
@@ -507,4 +614,5 @@
                       last_document=None):
 
+        self.assert_running()
         job = self.get_job(job_id)
         job.spool(document)
@@ -515,4 +623,6 @@
                  requesting_user_name=None, compression=None,
                  last_document=None):
+
+        self.assert_running()
         job = self.get_job(job_id)
         # XXX: need to validate URI
@@ -522,4 +632,6 @@
     @sync
     def get_job_attributes(self, job_id, requested_attributes=None):
+
+        self.assert_running()
         if requested_attributes is None:
             requested = self.job_attributes
@@ -534,4 +646,6 @@
     @sync
     def set_job_attributes(self, job_id, attributes):
+
+        self.assert_running()
         job = self.get_job(job_id)
         for attr in attributes:
@@ -545,4 +659,6 @@
     @sync
     def restart_job(self, job_id, requesting_user_name=None):
+
+        self.assert_running()
         job = self.get_job(job_id)
         try:
@@ -561,4 +677,5 @@
         # completes, this one will go next
         
+        self.assert_running()
         job = self.get_job(job_id)
         job.priority = 1 # XXX we need to actually do something
Index: server/lib/gutenbach/server/requests.py
===================================================================
--- server/lib/gutenbach/server/requests.py	(revision 079ef1192829d4f72566f32fd9e4d6529e29e186)
+++ server/lib/gutenbach/server/requests.py	(revision b77e57e2c3ea3fd3655c459b6f067fe23624ad22)
@@ -169,4 +169,5 @@
 
         """
+
         operation = request.attribute_groups[0]
         document = request.data        
@@ -178,6 +179,4 @@
         compression = None
         last_document = None
-
-
         # requested printer uri
         if 'printer-uri' not in operation:
Index: server/test/server/job.py
===================================================================
--- server/test/server/job.py	(revision bd5bffcd3ecf6e7bb5aefc5caae54483392c5f06)
+++ server/test/server/job.py	(revision 0494098be71273aa720ffe002e05a27ae68284e3)
@@ -201,6 +201,20 @@
 
     def testRestart(self):
-        # XXX: Todo
-        pass
+        self.assertRaises(errors.InvalidJobStateException, self.job.restart)
+
+        self.job.play()
+        self.assertTrue(self.job.is_playing)
+        self.assertFalse(self.job.is_done)
+
+        self.assertRaises(errors.InvalidJobStateException, self.job.restart)
+
+        self.job.cancel()
+        self.assertFalse(self.job.is_playing)
+        self.assertTrue(self.job.is_done)
+        self.assertTrue(self.job.is_cancelled)
+        self.assertFalse(self.job.is_aborted)
+
+        self.job.restart()
+        self.assertTrue(self.job.is_ready)
 
 if __name__ == "__main__":
