Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision ce2abc5b3d0f21e3ba1b4cfb8e693f55bb957284)
+++ server/lib/gutenbach/server/job.py	(revision b01b6d11a9f096abe2a8f080b7ea0aeb9b50edbb)
@@ -1,3 +1,4 @@
 from . import InvalidJobException, InvalidPrinterStateException
+from gutenbach.ipp import JobStates as States
 import os
 import gutenbach.ipp as ipp
@@ -21,85 +22,100 @@
     ]
 
-    def __init__(self, jid, printer, creator="", name="", size=0):
-	"""Initialize a Gutenbach job.
-
-	This sets the status to 'initializing' and optionally sets the
-	document to print to the value of document.
+    def __init__(self, job_id=-1, printer=None, creator=None, name=None, size=None):
+	"""Create an empty Gutenbach job.
 
 	"""
 
-	self.jid      = jid
-	self.printer  = printer
-
-        self.creator  = creator
-        self.name     = name
-        self.size     = size
+	self.printer = printer
+        self.player = None
+
+	self.id = job_id
+        self.creator = creator
+        self.name = name
+        self.size = size
+	self.status = States.HELD
 
         self.document = None
         self.document_name = None
-        self.player = None
-
-	self.status   = ipp.JobStates.HELD
-
-    def __getattr__(self, attr):
+        self.document_format = None
+        self.document_natural_language = None
+        self.compression = None
+    
+    def __repr__(self):
+	return str(self)
+
+    def __str__(self):
+        return "<Job %d '%s'>" % (self.id, self.name)
+
+    ######################################################################
+    ###                          Properties                            ###
+    ######################################################################
+
+    @property
+    def id(self):
+        """Unique job identifier.  Should be a positive integer,
+        except when unassigned, when it defaults to -1.
+        
+        """
+        return self._id
+    @id.setter
+    def id(self, val):
         try:
-            return self.__getattribute__(attr)
-        except AttributeError:
-            pass
-        return self.__getattribute__(attr.replace("-", "_"))
-
-    def __hasattr__(self, attr):
+            self._id = int(val)
+        except TypeError:
+            self._id = -1
+
+    @property
+    def creator(self):
+        """The user who created the job; analogous to the IPP
+        requesting-user-name.
+
+        """
+        return self._creator
+    @creator.setter
+    def creator(self, val):
+        if val is None:
+            self._creator = ""
+        else:
+            self._creator = str(val)
+
+    @property
+    def name(self):
+        """The job's name.
+
+        """
+        return self._name
+    @name.setter
+    def name(self, val):
+        if val is None:
+            self._name = ""
+        else:
+            self._name = str(val)
+
+    @property
+    def size(self):
+        """The size of the job in bytes.
+
+        """
+        if self.document:
+            size = os.path.getsize(self.document.name)
+        else:
+            size = self._size
+        return size
+    @size.setter
+    def size(self, val):
         try:
-            getattr(self, attr)
-            return True
-        except AttributeError:
-            return False
-
-    #### Job attributes
-
-    @property
-    def job_id(self):
-        return ipp.JobId(self.jid)
-
-    @property
-    def job_name(self):
-        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):
-        return ipp.JobKOctets(self.size)
-
-    @property
-    def job_state(self):
-        return ipp.JobState(self.status)
-
-    @property
-    def job_printer_uri(self):
-        return ipp.JobPrinterUri(self.printer.uri)
-
-    def get_job_attributes(self, request=None):
-        if request and 'requested-attributes' in request:
-            requested = []
-            for value in request['requested-attributes'].values:
-                if value.value in self.attributes:
-                    requested.append(value.value)
-        else:
-            requested = self.attributes
-            
-        attributes = [getattr(self, attr) for attr in requested]
-        return attributes
-    
-    #######
+            self._size = int(val)
+        except TypeError:
+            self._size = 0
+
+    ######################################################################
+    ###                            Methods                             ###
+    ######################################################################
 
     def play(self):
         logger.info("playing job %s" % str(self))
 	# TODO: add external call to music player
-	self.status = ipp.JobStates.PROCESSING
+	self.status = States.PROCESSING
         self.player = subprocess.Popen(
             "/usr/bin/mplayer -quiet %s" % self.document.name,
@@ -114,13 +130,89 @@
         #logger.debug(self.player.stdout)
         self.player = None
-	self.printer.complete_job(self.jid)
+	self.printer.complete_job(self.id)
 
     def finish(self):
         logger.info("finished job %s" % str(self))
-	self.status = ipp.JobStates.COMPLETE
-
-    def __repr__(self):
-	return str(self)
-
-    def __str__(self):
-        return "<Job %d '%s'>" % (self.jid if self.jid is not None else -1, self.name)
+	self.status = States.COMPLETE
+
+    ######################################################################
+    ###                        IPP Attributes                          ###
+    ######################################################################
+
+    @property
+    def job_id(self):
+        return ipp.JobId(self.id)
+
+    @property
+    def job_name(self):
+        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):
+        return ipp.JobKOctets(self.size)
+
+    @property
+    def job_state(self):
+        return ipp.JobState(self.status)
+
+    @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.status != States.HELD:
+            raise InvalidJobStateException(self.status)
+        
+        self.document = document
+        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.status = 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
