Index: server/lib/gutenbach/server/__init__.py
===================================================================
--- server/lib/gutenbach/server/__init__.py	(revision d21198f3d35a65b2e9854e95aed74003e47acb36)
+++ server/lib/gutenbach/server/__init__.py	(revision 33ea5054308a544bb5f939b99ad15f59fa4b6d7a)
@@ -4,6 +4,6 @@
 __all__.extend(errors.__all__)
 
-from job import Job
-__all__.append('Job')
+from job import GutenbachJob
+__all__.append('GutenbachJob')
 
 from printer import GutenbachPrinter
Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision 34a4e5d730962d108163c14fafd961f93db9c5db)
+++ server/lib/gutenbach/server/job.py	(revision 33ea5054308a544bb5f939b99ad15f59fa4b6d7a)
@@ -1,30 +1,18 @@
-from . import InvalidJobStateException, MissingDataException
+from .errors import InvalidJobStateException, MissingDataException
 from .player import Player
 from gutenbach.ipp import JobStates as States
+import logging
 import os
-import gutenbach.ipp as ipp
-import logging
 
 # initialize logger
 logger = logging.getLogger(__name__)
 
-class Job(object):
+class GutenbachJob(object):
 
-    # for IPP
-    attributes = [
-        "job-id",
-        "job-name",
-        "job-originating-user-name",
-        "job-k-octets",
-        "job-state",
-        "job-printer-uri"
-    ]
-
-    def __init__(self, job_id=-1, printer=None, creator=None, name=None, size=None):
+    def __init__(self, job_id=None, creator=None, name=None):
 	"""Create an empty Gutenbach job.
 
 	"""
 
-	self.printer = printer
         self.player = None
 
@@ -32,13 +20,8 @@
         self.creator = creator
         self.name = name
-        self.size = size
 	self.state = States.HELD
         self.priority = 1
+        self.document = None
 
-        self.document_name = None
-        self.document_format = None
-        self.document_natural_language = None
-        self.compression = None
-    
     def __repr__(self):
 	return str(self)
@@ -67,13 +50,4 @@
         except TypeError:
             self._id = -1
-
-    @property
-    def uri(self):
-        return self.uris[0]
-
-    @property
-    def uris(self):
-        return ["ipp://localhost/jobs/%d" % self.id,
-                "ipp://localhost:8000/jobs/%d" % self.id]
 
     @property
@@ -109,11 +83,9 @@
 
         """
-        return self._size
-    @size.setter
-    def size(self, val):
         try:
-            self._size = int(val)
-        except TypeError:
-            self._size = 0
+            size = os.path.getsize(self.document)
+        except:
+            size = 0
+        return size
 
     @property
@@ -135,6 +107,24 @@
     ######################################################################
 
+    def spool(self, document, username=None):
+        if self.state != States.HELD:
+            raise InvalidJobStateException(self.state)
+
+        self.document = document.name
+        self.player = Player(document)
+        self.creator = username
+        self.state = States.PENDING
+
+        logger.debug("document for job %d is '%s'" % (self.id, self.document))
+
+
     def play(self):
-        """Non-blocking play function.
+        """Non-blocking play function.  Sets the job state to
+        PROCESSING.
+
+        Raises
+        ------
+        InvalidJobStateException
+            If the job is not ready to be played.
 
         """
@@ -142,122 +132,58 @@
         # make sure the job is waiting to be played and that it's
         # valid
-        if self.state != States.PENDING:
+        if not self.is_ready:
             raise InvalidJobStateException(self.state)
         
         # and set the state to processing if we're good to go
         logger.info("playing job %s" % str(self))
+
+        def _completed():
+            logger.info("completed job %s" % str(self))
+            self.state = States.COMPLETE
+            self.player = None
+
 	self.state = States.PROCESSING
-        self.player.callback = self._completed
+        self.player.callback = _completed
         self.player.start()
 
     def pause(self):
-        if self.is_playing:
-            self.player.mplayer_pause()
-            self.state = States.STOPPED
+        """Non-blocking pause function.  Sets the job state to
+        STOPPED.
+
+        """
+        
+        if not self.is_playing:
+            raise InvalidJobStateException(self.state)
+        
+        self.player.mplayer_pause()
+        self.state = States.STOPPED
 
     def cancel(self):
+        def _canceled():
+            logger.info("canceled job %s" % str(self))
+            self.state = States.CANCELLED
+            self.player = None
+
         if self.is_playing:
-            self.player.callback = self._canceled
+            self.player.callback = _canceled
             self.player.mplayer_stop()
+        elif self.is_finished:
+            raise InvalidJobStateException(self.state)
+        
         self.state = States.CANCELLED
 
     def abort(self):
+        def _aborted():
+            logger.info("aborted job %s" % str(self))
+            self.state = States.ABORTED
+            self.player = None
+
         if self.is_playing:
-            self.player.callback = self._aborted
+            self.player.callback = _aborted
             self.player.mplayer_stop()
-        self.state = states.ABORTED
-
-    def _completed(self):
-        logger.info("completed job %s" % str(self))
-	self.state = States.COMPLETE
-        self.player = None
-
-    def _canceled(self):
-        logger.info("canceled job %s" % str(self))
-        self.state = States.CANCELLED
-        self.player = None
-
-    def _aborted(self):
-        logger.info("aborted job %s" % str(self))
+        elif self.is_finished:
+            raise InvalidJobStateException(self.state)
+        
         self.state = States.ABORTED
-        self.player = None
-
-    ######################################################################
-    ###                        IPP Attributes                          ###
-    ######################################################################
-
-    @property
-    def job_id(self):
-        return ipp.JobId(self.id)
-
-    @property
-    def job_name(self):
-        return ipp.JobName(self.name)
-
-    @property
-    def job_originating_user_name(self):
-        return ipp.JobOriginatingUserName(self.creator)
-
-    @property
-    def job_k_octets(self):
-        return ipp.JobKOctets(self.size)
-
-    @property
-    def job_state(self):
-        return ipp.JobState(self.state)
-
-    @property
-    def job_printer_uri(self):
-        return ipp.JobPrinterUri(self.printer.uri)
 
 
-    ######################################################################
-    ###                        IPP Operations                          ###
-    ######################################################################
-
-    def cancel_job(self):
-        pass
-
-    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.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)
-        self.document_natural_language = str(document_natural_language)
-        self.creator = str(requesting_user_name)
-        self.compression = str(compression)
-        self.state = States.PENDING
-
-        logger.debug("document for job %d is '%s'" % (self.id, self.document_name))
-
-    def send_uri(self):
-        pass
-
-    def get_job_attributes(self, requested_attributes=None):
-        if requested_attributes is None:
-            requested = self.attributes
-        else:
-            requested = [a for a in self.attributes if a in requested_attributes]
-
-        _attributes = [attr.replace("-", "_") for attr in requested]
-        attributes = [getattr(self, attr) for attr in _attributes]
-        return attributes
-
-    def set_job_attributes(self):
-        pass
-
-    def restart_job(self):
-        pass
-
-    def promote_job(self):
-        pass
Index: server/lib/gutenbach/server/player.py
===================================================================
--- server/lib/gutenbach/server/player.py	(revision eee389a2ef90375dc33268c1828d860ef7be2907)
+++ server/lib/gutenbach/server/player.py	(revision 33ea5054308a544bb5f939b99ad15f59fa4b6d7a)
@@ -41,4 +41,7 @@
                 self.callback()
 
+        self.fh.close()
+        self.fh = None
+
     def mplayer_play(self):
         logger.info("playing file '%s'" % self.fh.name)
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision 34a4e5d730962d108163c14fafd961f93db9c5db)
+++ server/lib/gutenbach/server/printer.py	(revision 33ea5054308a544bb5f939b99ad15f59fa4b6d7a)
@@ -1,4 +1,4 @@
-from . import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
-from . import Job
+from .errors import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
+from .job import GutenbachJob
 from gutenbach.ipp import PrinterStates as States
 import gutenbach.ipp as ipp
@@ -17,5 +17,5 @@
 
     # for IPP
-    attributes = [
+    printer_attributes = [
         "printer-uri-supported",
         "uri-authentication-supported",
@@ -41,10 +41,30 @@
     ]
 
+    job_attributes = [
+        "job-id",
+        "job-name",
+        "job-originating-user-name",
+        "job-k-octets",
+        "job-state",
+        "job-printer-uri"
+    ]
+
     operations = [
         "print-job",
-        "complete-job",
-        "start-job",
-        "get-job",
+        "validate-job",
         "get-jobs",
+        "print-uri",
+        "create-job",
+        "pause-printer",
+        "resume-printer",
+        "get-printer-attributes",
+        "set-printer-attributes",
+        "cancel-job",
+        "send-document",
+        "send-uri",
+        "get-job-attributes",
+        "set-job-attributes",
+        "restart-job",
+        "promote-job"
     ]
         
@@ -73,41 +93,4 @@
     def __str__(self):
         return "<Printer '%s'>" % self.name 
-
-    ######################################################################
-    ###                          Properties                            ###
-    ######################################################################
-
-    @property
-    def uris(self):
-        uris = ["ipp://localhost:8000/printers/" + self.name,
-                "ipp://localhost/printers/" + self.name]
-        return uris
-    
-    @property
-    def uri(self):
-        return self.uris[0]
-
-    @property
-    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):
@@ -124,4 +107,41 @@
                     sys.exit(1)
             time.sleep(0.1)
+
+    ######################################################################
+    ###                          Properties                            ###
+    ######################################################################
+
+    @property
+    def uris(self):
+        uris = ["ipp://localhost:8000/printers/" + self.name,
+                "ipp://localhost/printers/" + self.name]
+        return uris
+    
+    @property
+    def uri(self):
+        return self.uris[0]
+
+    @property
+    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 start_job(self):
@@ -150,7 +170,4 @@
                 self.current_job = None
 
-    def stop(self):
-        pass
-
     def get_job(self, job_id):
         with self.lock:
@@ -249,4 +266,31 @@
         return ipp.MultipleDocumentJobsSupported(False)
 
+    ######################################################################
+    ###                      Job IPP Attributes                        ###
+    ######################################################################
+
+    def job_id(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobId(job.id)
+
+    def job_name(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobName(job.name)
+
+    def job_originating_user_name(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobOriginatingUserName(job.creator)
+
+    def job_k_octets(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobKOctets(job.size)
+
+    def job_state(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobState(job.state)
+
+    def job_printer_uri(self, job_id):
+        job = self.get_job(job_id)
+        return ipp.JobPrinterUri(self.uri)
 
     ######################################################################
@@ -260,5 +304,7 @@
         pass
 
-    def get_jobs(self, requesting_user_name="", which_jobs=None):
+    def get_jobs(self, requesting_user_name=None, which_jobs=None,
+                 requested_attributes=None):
+        
         # Filter by the which-jobs attribute
         if which_jobs is None:
@@ -278,6 +324,10 @@
         else:
             user_jobs = [job for job in jobs if job.creator == requesting_user_name]
-        
-        return user_jobs
+
+        # Get the attributes of each job
+        job_attrs = [self.get_job_attributes(
+            job.id, requested_attributes=requested_attributes) for job in user_jobs]
+        
+        return job_attrs
 
     def print_uri(self):
@@ -288,14 +338,13 @@
         self._next_job_id += 1
         
-        job = Job(job_id,
-                  self,
-                  creator=requesting_user_name,
-                  name=job_name,
-                  size=job_k_octets)
+        job = GutenbachJob(
+            job_id,
+            creator=requesting_user_name,
+            name=job_name)
         
         self.jobs[job_id] = job
         self.pending_jobs.append(job_id)
         
-        return job
+        return job_id
 
     def pause_printer(self):
@@ -307,7 +356,8 @@
     def get_printer_attributes(self, requested_attributes=None):
         if requested_attributes is None:
-            requested = self.attributes
+            requested = self.printer_attributes
         else:
-            requested = [a for a in self.attributes if a in requested_attributes]
+            requested = [a for a in self.printer_attributes \
+                         if a in requested_attributes]
 
         _attributes = [attr.replace("-", "_") for attr in requested]
@@ -317,2 +367,41 @@
     def set_printer_attributes(self):
         pass
+
+    def cancel_job(self, job_id, requesting_user_name=None):
+        job = self.get_job(job_id)
+        try:
+            job.cancel()
+        except InvalidJobStateException:
+            # XXX
+            raise
+
+    def send_document(self, job_id, document, document_name=None,
+                      document_format=None, document_natural_language=None,
+                      requesting_user_name=None, compression=None,
+                      last_document=None):
+
+        job = self.get_job(job_id)
+        job.spool(document, username=requesting_user_name)
+
+    def send_uri(self):
+        pass
+
+    def get_job_attributes(self, job_id, requested_attributes=None):
+        if requested_attributes is None:
+            requested = self.job_attributes
+        else:
+            requested = [a for a in self.job_attributes \
+                         if a in requested_attributes]
+
+        _attributes = [attr.replace("-", "_") for attr in requested]
+        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
+        return attributes
+
+    def set_job_attributes(self):
+        pass
+
+    def restart_job(self):
+        pass
+
+    def promote_job(self):
+        pass
Index: server/lib/gutenbach/server/requests.py
===================================================================
--- server/lib/gutenbach/server/requests.py	(revision 34a4e5d730962d108163c14fafd961f93db9c5db)
+++ server/lib/gutenbach/server/requests.py	(revision 33ea5054308a544bb5f939b99ad15f59fa4b6d7a)
@@ -258,9 +258,9 @@
             
         # get the job attributes and add them to the response
-        jobs = self.printer.get_jobs(
+        job_attrs = self.printer.get_jobs(
             which_jobs=which_jobs,
-            requesting_user_name=user)
-        for job in jobs:
-            attrs = job.get_job_attributes(requested_attributes=attributes)
+            requesting_user_name=user,
+            requested_attributes=attributes)
+        for attrs in job_attrs:
             response.attribute_groups.append(ipp.AttributeGroup(
                 ipp.AttributeTags.JOB, attrs))
@@ -354,10 +354,11 @@
 
         # get attributes from the printer and add to response
-        job = self.printer.create_job(
+        job_id = self.printer.create_job(
             requesting_user_name=requesting_user_name,
             job_name=job_name,
             job_k_octets=job_k_octets)
+        attrs = self.printer.get_job_attributes(job_id)
         response.attribute_groups.append(ipp.AttributeGroup(
-            ipp.AttributeTags.JOB, job.get_job_attributes()))
+            ipp.AttributeTags.JOB, attrs))
     
     @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
@@ -536,10 +537,9 @@
 
         if 'requesting-user-name' in operation:
-            user_name = verify_attribute(
+            requesting_user_name = verify_attribute(
                 operation['requesting-user-name'], ipp.RequestingUserName)[0]
 
         try:
-            job = self.printer.get_job(job_id)
-            job.cancel()
+            self.printer.cancel_job(job_id, requesting_user_name=requesting_user_name)
         except InvalidJobException:
             raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
@@ -681,14 +681,14 @@
 
         try:
-            job = self.printer.get_job(job_id)
-            job.send_document(
+            self.printer.send_document(
+                job_id,
                 request.data,
-                requesting_user_name=user_name,
                 document_name=document_name,
-                compression=compression,
                 document_format=document_format,
                 document_natural_language=document_natural_language,
+                requesting_user_name=user_name,
+                compression=compression,
                 last_document=last_document)
-            attrs = job.get_job_attributes()
+            attrs = self.printer.get_job_attributes(job_id)
         except InvalidJobException:
             raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
@@ -796,6 +796,7 @@
         # get the job attributes and add them to the response
         try:
-            job = self.printer.get_job(job_id)
-            attrs = job.get_job_attributes(requested_attributes=requested_attributes)
+            attrs = self.printer.get_job_attributes(
+                job_id,
+                requested_attributes=requested_attributes)
         except InvalidJobException:
             raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
