Index: /pkipplib/tags/0.07/notifiers/samplenotifier
===================================================================
--- /pkipplib/tags/0.07/notifiers/samplenotifier (revision 35)
+++ /pkipplib/tags/0.07/notifiers/samplenotifier (revision 35)
@@ -0,0 +1,41 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+
+"""This command can be placed in /usr/lib/cups/notifier and
+used as the notifications' recipient when creating IPP subscriptions.
+
+It will automatically ensures that each time a printer is added or
+removed from CUPS it is also added or removed from PyKota's database.
+
+IMPORTANT : because of a bug in CUPS 1.2.1, this command only works
+the first time a notification is sent."""
+
+import sys
+import os
+import fcntl
+
+from pkipplib import pkipplib
+
+if __name__ == "__main__" :
+    #
+    # First thing we do is put stdin in non-blocking mode.
+    fd = sys.stdin.fileno()
+    fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, 
+                    fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+    # then we read the notification CUPS sent us to our stdin
+    notification = pkipplib.IPPRequest(sys.stdin.read())
+    
+    # now we parse it
+    notification.parse()
+    
+    # then we act one way or another, depending on the event received.
+    event = notification.event_notification["notify-subscribed-event"][0][1]
+    if event in ("printer-added", "printer-deleted") :
+        printername = notification.event_notification["printer-name"][0][1]
+        if event.endswith("-added") :
+            action = "add"
+        else :    
+            action = "delete"
+        os.system('/usr/bin/pkprinters --%s "%s"' % (action, printername))
Index: /pkipplib/tags/0.07/tests/test.py
===================================================================
--- /pkipplib/tags/0.07/tests/test.py (revision 28)
+++ /pkipplib/tags/0.07/tests/test.py (revision 28)
@@ -0,0 +1,36 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP and CUPS support for Python
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+
+import sys
+
+sys.path.insert(0, "../pkipplib")
+import pkipplib        
+    
+cups = pkipplib.CUPS()
+answer = cups.createSubscription("ipp://localhost/", ["printer-added", "printer-deleted"],
+                                                     userdata="samplenotifier:blah",
+                                                     recipient="samplenotifier",
+                                                     charset="utf-8")
+print answer
+
+#print answer.operation["attributes-charset"]
+
Index: /pkipplib/tags/0.07/setup.py
===================================================================
--- /pkipplib/tags/0.07/setup.py (revision 33)
+++ /pkipplib/tags/0.07/setup.py (revision 33)
@@ -0,0 +1,38 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP and CUPS support for Python
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
+
+import os
+import imp
+from distutils.core import setup
+
+version = imp.load_source("version", os.path.join("pkipplib", "version.py"))
+
+setup(name = "pkipplib", version = version.__version__,
+      license = "GNU GPL",
+      description = version.__doc__,
+      author = "Jerome Alet",
+      author_email = "alet@librelogiciel.com",
+      url = "http://www.pykota.com/software/pkipplib",
+      packages = [ "pkipplib" ],
+      scripts = [os.path.join("bin", "pksubscribe")])
+
Index: /pkipplib/tags/0.07/COPYING
===================================================================
--- /pkipplib/tags/0.07/COPYING (revision 3)
+++ /pkipplib/tags/0.07/COPYING (revision 3)
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /pkipplib/tags/0.07/bin/pksubscribe
===================================================================
--- /pkipplib/tags/0.07/bin/pksubscribe (revision 32)
+++ /pkipplib/tags/0.07/bin/pksubscribe (revision 32)
@@ -0,0 +1,149 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP and CUPS support for Python
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+
+"""pksubscribe is a command line tool which can be used to create
+or delete IPP subscriptions."""
+
+import sys
+import locale
+import gettext
+import optparse
+
+from pkipplib import pkipplib        
+    
+if __name__ == "__main__" :    
+    try :
+        locale.setlocale(locale.LC_ALL, "")
+    except (locale.Error, IOError) :
+        sys.stderr.write("Problem while setting locale.\n")
+    try :
+        gettext.install("pksubscribe")
+    except :
+        gettext.NullTranslations().install()
+    parser = optparse.OptionParser(usage="pksubscribe [options] [subscriptions ids]")
+    parser.add_option("-v", "--version", 
+                            action="store_true", 
+                            dest="version",
+                            help=_("show pksubscribe's version number and exit."))
+    parser.add_option("-c", "--cups", 
+                            default="http://localhost:631", 
+                            dest="cups",
+                            help=_("the CUPS server to connect to. Defaults to http://localhost:631"))
+    parser.add_option("-d", "--debug", 
+                            action="store_true", 
+                            dest="debug",
+                            help=_("activate debug mode."))
+    parser.add_option("-X", "--delete", 
+                            action="store_true", 
+                            dest="delete",
+                            help=_("deletes subscriptions."))
+    parser.add_option("-p", "--printer", 
+                            dest="printer",
+                            help=_("the printer's name for a printer subscription."))
+    parser.add_option("-j", "--job", 
+                            type="int", 
+                            dest="job",
+                            help=_("the job's id for a job subscripition."))
+    parser.add_option("-r", "--recipient",                        
+                            dest="recipient",
+                            help=_("the recipient's uri."))
+    parser.add_option("-C", "--charset",                        
+                            dest="charset",
+                            help=_("the charset to use in notifications sent for this subscription."))
+    parser.add_option("-L", "--language",                        
+                            dest="language",
+                            help=_("the language to use in notifications sent for this subscription."))
+    parser.add_option("-u", "--userdata",                        
+                            dest="userdata",
+                            help=_("the user's data to use in notifications for this subscription."))
+    parser.add_option("-U", "--username",                        
+                            dest="username",
+                            help=_("the user's name to use when connecting to the CUPS server."))
+    parser.add_option("-W", "--password",                        
+                            dest="password",
+                            help=_("the user's password to use when connecting to the CUPS server."))
+    parser.add_option("-E", "--events",                        
+                            dest="events",
+                            help=_("a comma separated list of events to subscribe to."))
+    parser.add_option("-P", "--pullmethod",                        
+                            dest="pullmethod",
+                            help=_("the optional pull method's name."))
+    parser.add_option("-D", "--duration",
+                            type="int",
+                            dest="duration",
+                            help=_("the duration of the subscription."))
+    parser.add_option("-I", "--interval",                        
+                            type="int",
+                            dest="interval",
+                            help=_("the time interval of the subscription."))
+                            
+    (options, arguments) = parser.parse_args()
+    if options.version :
+        print "pksubscribe v%(__version__)s" % globals()
+    else :
+        if not options.events and not options.delete :
+            sys.stderr.write(_("You MUST pass a list of events to subscribe to.\n"))
+        elif not options.recipient and not options.delete :    
+            sys.stderr.write(_("You MUST pass a recipient for the subscription.\n"))
+        elif options.delete and not arguments :    
+            sys.stderr.write(_("You MUST pass a subscriptions ids at the end of your command line.\n"))
+        else :    
+            cups = pkipplib.CUPS(options.cups,
+                                 options.username,
+                                 options.password,
+                                 debug=options.debug)
+            baseurl = options.cups.replace("http://", "ipp://")
+            if baseurl.endswith(":631") : 
+                baseurl = baseurl[:-4]
+            if options.printer :
+                url = "%s/printers/%s" % (baseurl, options.printer)
+            elif options.job :     
+                url = "%s/jobs/%i" % (baseurl, options.job)
+            else :    
+                url = baseurl
+            if not options.delete :    
+                answer = cups.createSubscription(url,
+                                                 [e.strip() for e in options.events.split(",")],
+                                                 userdata=options.userdata,
+                                                 recipient=options.recipient,
+                                                 pullmethod=options.pullmethod,
+                                                 charset=options.charset,
+                                                 naturallanguage=options.language,
+                                                 leaseduration=options.duration,
+                                                 timeinterval=options.interval,
+                                                 jobid=options.job)
+                try :                                 
+                    subscriptionid = answer.subscription["notify-subscription-id"][0][1]
+                except KeyError :
+                    sys.stderr.write("%s\n" % answer.operation["status-message"][0][1])
+                else :    
+                    print "Subscription %i registered." % subscriptionid
+            else :        
+                for subid in [int(arg) for arg in arguments] :
+                    answer = cups.cancelSubscription(url, subid, options.job)
+                    try :
+                        error = answer.operation["status-message"][0][1]
+                    except KeyError :    
+                        print "Subscription %i cancelled." % subid
+                    else :
+                        sys.stderr.write("%s\n" % error)
+
Index: /pkipplib/tags/0.07/pkipplib/pkipplib.py
===================================================================
--- /pkipplib/tags/0.07/pkipplib/pkipplib.py (revision 35)
+++ /pkipplib/tags/0.07/pkipplib/pkipplib.py (revision 35)
@@ -0,0 +1,796 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP and CUPS support for Python
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
+#
+
+import sys
+import os
+import urllib2
+import socket
+from struct import pack, unpack
+
+IPP_VERSION = "1.1"     # default version number
+
+IPP_PORT = 631
+
+IPP_MAX_NAME = 256
+IPP_MAX_VALUES = 8
+
+IPP_TAG_ZERO = 0x00
+IPP_TAG_OPERATION = 0x01
+IPP_TAG_JOB = 0x02
+IPP_TAG_END = 0x03
+IPP_TAG_PRINTER = 0x04
+IPP_TAG_UNSUPPORTED_GROUP = 0x05
+IPP_TAG_SUBSCRIPTION = 0x06
+IPP_TAG_EVENT_NOTIFICATION = 0x07
+IPP_TAG_UNSUPPORTED_VALUE = 0x10
+IPP_TAG_DEFAULT = 0x11
+IPP_TAG_UNKNOWN = 0x12
+IPP_TAG_NOVALUE = 0x13
+IPP_TAG_NOTSETTABLE = 0x15
+IPP_TAG_DELETEATTR = 0x16
+IPP_TAG_ADMINDEFINE = 0x17
+IPP_TAG_INTEGER = 0x21
+IPP_TAG_BOOLEAN = 0x22
+IPP_TAG_ENUM = 0x23
+IPP_TAG_STRING = 0x30
+IPP_TAG_DATE = 0x31
+IPP_TAG_RESOLUTION = 0x32
+IPP_TAG_RANGE = 0x33
+IPP_TAG_BEGIN_COLLECTION = 0x34
+IPP_TAG_TEXTLANG = 0x35
+IPP_TAG_NAMELANG = 0x36
+IPP_TAG_END_COLLECTION = 0x37
+IPP_TAG_TEXT = 0x41
+IPP_TAG_NAME = 0x42
+IPP_TAG_KEYWORD = 0x44
+IPP_TAG_URI = 0x45
+IPP_TAG_URISCHEME = 0x46
+IPP_TAG_CHARSET = 0x47
+IPP_TAG_LANGUAGE = 0x48
+IPP_TAG_MIMETYPE = 0x49
+IPP_TAG_MEMBERNAME = 0x4a
+IPP_TAG_MASK = 0x7fffffff
+IPP_TAG_COPY = -0x7fffffff-1
+
+IPP_RES_PER_INCH = 3
+IPP_RES_PER_CM = 4
+
+IPP_FINISHINGS_NONE = 3
+IPP_FINISHINGS_STAPLE = 4
+IPP_FINISHINGS_PUNCH = 5
+IPP_FINISHINGS_COVER = 6
+IPP_FINISHINGS_BIND = 7
+IPP_FINISHINGS_SADDLE_STITCH = 8
+IPP_FINISHINGS_EDGE_STITCH = 9
+IPP_FINISHINGS_FOLD = 10
+IPP_FINISHINGS_TRIM = 11
+IPP_FINISHINGS_BALE = 12
+IPP_FINISHINGS_BOOKLET_MAKER = 13
+IPP_FINISHINGS_JOB_OFFSET = 14
+IPP_FINISHINGS_STAPLE_TOP_LEFT = 20
+IPP_FINISHINGS_STAPLE_BOTTOM_LEFT = 21
+IPP_FINISHINGS_STAPLE_TOP_RIGHT = 22
+IPP_FINISHINGS_STAPLE_BOTTOM_RIGHT = 23
+IPP_FINISHINGS_EDGE_STITCH_LEFT = 24
+IPP_FINISHINGS_EDGE_STITCH_TOP = 25
+IPP_FINISHINGS_EDGE_STITCH_RIGHT = 26
+IPP_FINISHINGS_EDGE_STITCH_BOTTOM = 27
+IPP_FINISHINGS_STAPLE_DUAL_LEFT = 28
+IPP_FINISHINGS_STAPLE_DUAL_TOP = 29
+IPP_FINISHINGS_STAPLE_DUAL_RIGHT = 30
+IPP_FINISHINGS_STAPLE_DUAL_BOTTOM = 31
+IPP_FINISHINGS_BIND_LEFT = 50
+IPP_FINISHINGS_BIND_TOP = 51
+IPP_FINISHINGS_BIND_RIGHT = 52
+IPP_FINISHINGS_BIND_BOTTO = 53
+
+IPP_PORTRAIT = 3
+IPP_LANDSCAPE = 4
+IPP_REVERSE_LANDSCAPE = 5
+IPP_REVERSE_PORTRAIT = 6
+
+IPP_QUALITY_DRAFT = 3
+IPP_QUALITY_NORMAL = 4
+IPP_QUALITY_HIGH = 5
+
+IPP_JOB_PENDING = 3
+IPP_JOB_HELD = 4
+IPP_JOB_PROCESSING = 5
+IPP_JOB_STOPPED = 6
+IPP_JOB_CANCELLED = 7
+IPP_JOB_ABORTED = 8
+IPP_JOB_COMPLETE = 9
+
+IPP_PRINTER_IDLE = 3
+IPP_PRINTER_PROCESSING = 4
+IPP_PRINTER_STOPPED = 5
+
+IPP_ERROR = -1
+IPP_IDLE = 0
+IPP_HEADER = 1
+IPP_ATTRIBUTE = 2
+IPP_DATA = 3
+
+IPP_PRINT_JOB = 0x0002
+IPP_PRINT_URI = 0x0003
+IPP_VALIDATE_JOB = 0x0004
+IPP_CREATE_JOB = 0x0005
+IPP_SEND_DOCUMENT = 0x0006
+IPP_SEND_URI = 0x0007
+IPP_CANCEL_JOB = 0x0008
+IPP_GET_JOB_ATTRIBUTES = 0x0009
+IPP_GET_JOBS = 0x000a
+IPP_GET_PRINTER_ATTRIBUTES = 0x000b
+IPP_HOLD_JOB = 0x000c
+IPP_RELEASE_JOB = 0x000d
+IPP_RESTART_JOB = 0x000e
+IPP_PAUSE_PRINTER = 0x0010
+IPP_RESUME_PRINTER = 0x0011
+IPP_PURGE_JOBS = 0x0012
+IPP_SET_PRINTER_ATTRIBUTES = 0x0013
+IPP_SET_JOB_ATTRIBUTES = 0x0014
+IPP_GET_PRINTER_SUPPORTED_VALUES = 0x0015
+IPP_CREATE_PRINTER_SUBSCRIPTION = 0x0016
+IPP_CREATE_JOB_SUBSCRIPTION = 0x0017
+IPP_GET_SUBSCRIPTION_ATTRIBUTES = 0x0018
+IPP_GET_SUBSCRIPTIONS = 0x0019
+IPP_RENEW_SUBSCRIPTION = 0x001a
+IPP_CANCEL_SUBSCRIPTION = 0x001b
+IPP_GET_NOTIFICATIONS = 0x001c
+IPP_SEND_NOTIFICATIONS = 0x001d
+IPP_GET_PRINT_SUPPORT_FILES = 0x0021
+IPP_ENABLE_PRINTER = 0x0022
+IPP_DISABLE_PRINTER = 0x0023
+IPP_PAUSE_PRINTER_AFTER_CURRENT_JOB = 0x0024
+IPP_HOLD_NEW_JOBS = 0x0025
+IPP_RELEASE_HELD_NEW_JOBS = 0x0026
+IPP_DEACTIVATE_PRINTER = 0x0027
+IPP_ACTIVATE_PRINTER = 0x0028
+IPP_RESTART_PRINTER = 0x0029
+IPP_SHUTDOWN_PRINTER = 0x002a
+IPP_STARTUP_PRINTER = 0x002b
+IPP_REPROCESS_JOB = 0x002c
+IPP_CANCEL_CURRENT_JOB = 0x002d
+IPP_SUSPEND_CURRENT_JOB = 0x002e
+IPP_RESUME_JOB = 0x002f
+IPP_PROMOTE_JOB = 0x0030
+IPP_SCHEDULE_JOB_AFTER = 0x0031
+IPP_PRIVATE = 0x4000
+CUPS_GET_DEFAULT = 0x4001
+CUPS_GET_PRINTERS = 0x4002
+CUPS_ADD_PRINTER = 0x4003
+CUPS_DELETE_PRINTER = 0x4004
+CUPS_GET_CLASSES = 0x4005
+CUPS_ADD_CLASS = 0x4006
+CUPS_DELETE_CLASS = 0x4007
+CUPS_ACCEPT_JOBS = 0x4008
+CUPS_REJECT_JOBS = 0x4009
+CUPS_SET_DEFAULT = 0x400a
+CUPS_GET_DEVICES = 0x400b
+CUPS_GET_PPDS = 0x400c
+CUPS_MOVE_JOB = 0x400d
+CUPS_AUTHENTICATE_JOB = 0x400e
+
+IPP_OK = 0x0000
+IPP_OK_SUBST = 0x0001
+IPP_OK_CONFLICT = 0x0002
+IPP_OK_IGNORED_SUBSCRIPTIONS = 0x0003
+IPP_OK_IGNORED_NOTIFICATIONS = 0x0004
+IPP_OK_TOO_MANY_EVENTS = 0x0005
+IPP_OK_BUT_CANCEL_SUBSCRIPTION = 0x0006
+IPP_REDIRECTION_OTHER_SITE = 0x0300
+IPP_BAD_REQUEST = 0x0400
+IPP_FORBIDDEN = 0x0401
+IPP_NOT_AUTHENTICATED = 0x0402
+IPP_NOT_AUTHORIZED = 0x0403
+IPP_NOT_POSSIBLE = 0x0404
+IPP_TIMEOUT = 0x0405
+IPP_NOT_FOUND = 0x0406
+IPP_GONE = 0x0407
+IPP_REQUEST_ENTITY = 0x0408
+IPP_REQUEST_VALUE = 0x0409
+IPP_DOCUMENT_FORMAT = 0x040a
+IPP_ATTRIBUTES = 0x040b
+IPP_URI_SCHEME = 0x040c
+IPP_CHARSET = 0x040d
+IPP_CONFLICT = 0x040e
+IPP_COMPRESSION_NOT_SUPPORTED = 0x040f
+IPP_COMPRESSION_ERROR = 0x0410
+IPP_DOCUMENT_FORMAT_ERROR = 0x0411
+IPP_DOCUMENT_ACCESS_ERROR = 0x0412
+IPP_ATTRIBUTES_NOT_SETTABLE = 0x0413
+IPP_IGNORED_ALL_SUBSCRIPTIONS = 0x0414
+IPP_TOO_MANY_SUBSCRIPTIONS = 0x0415
+IPP_IGNORED_ALL_NOTIFICATIONS = 0x0416
+IPP_PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417
+
+IPP_INTERNAL_ERROR = 0x0500
+IPP_OPERATION_NOT_SUPPORTED = 0x0501
+IPP_SERVICE_UNAVAILABLE = 0x0502
+IPP_VERSION_NOT_SUPPORTED = 0x0503
+IPP_DEVICE_ERROR = 0x0504
+IPP_TEMPORARY_ERROR = 0x0505
+IPP_NOT_ACCEPTING = 0x0506
+IPP_PRINTER_BUSY = 0x0507
+IPP_ERROR_JOB_CANCELLED = 0x0508
+IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509
+IPP_PRINTER_IS_DEACTIVATED = 0x50a
+  
+CUPS_PRINTER_LOCAL = 0x0000
+CUPS_PRINTER_CLASS = 0x0001
+CUPS_PRINTER_REMOTE = 0x0002
+CUPS_PRINTER_BW = 0x0004
+CUPS_PRINTER_COLOR = 0x0008
+CUPS_PRINTER_DUPLEX = 0x0010
+CUPS_PRINTER_STAPLE = 0x0020
+CUPS_PRINTER_COPIES = 0x0040
+CUPS_PRINTER_COLLATE = 0x0080
+CUPS_PRINTER_PUNCH = 0x0100
+CUPS_PRINTER_COVER = 0x0200
+CUPS_PRINTER_BIND = 0x0400
+CUPS_PRINTER_SORT = 0x0800
+CUPS_PRINTER_SMALL = 0x1000
+CUPS_PRINTER_MEDIUM = 0x2000
+CUPS_PRINTER_LARGE = 0x4000
+CUPS_PRINTER_VARIABLE = 0x8000
+CUPS_PRINTER_IMPLICIT = 0x1000
+CUPS_PRINTER_DEFAULT = 0x2000
+CUPS_PRINTER_FAX = 0x4000
+CUPS_PRINTER_REJECTING = 0x8000
+CUPS_PRINTER_DELETE = 0x1000
+CUPS_PRINTER_NOT_SHARED = 0x2000
+CUPS_PRINTER_AUTHENTICATED = 0x4000
+CUPS_PRINTER_COMMANDS = 0x8000
+CUPS_PRINTER_OPTIONS = 0xe6ff
+  
+  
+class IPPError(Exception) :
+    """An exception for IPP related stuff."""
+    def __init__(self, message = ""):
+        self.message = message
+        Exception.__init__(self, message)
+    def __repr__(self):
+        return self.message
+    __str__ = __repr__
+
+class FakeAttribute :
+    """Fakes an IPPRequest attribute to simplify usage syntax."""
+    def __init__(self, request, name) :
+        """Initializes the fake attribute."""
+        self.request = request
+        self.name = name
+        
+    def __setitem__(self, key, value) :
+        """Appends the value to the real attribute."""
+        attributeslist = getattr(self.request, "_%s_attributes" % self.name)
+        for i in range(len(attributeslist)) :
+            attribute = attributeslist[i]
+            for j in range(len(attribute)) :
+                (attrname, attrvalue) = attribute[j]
+                if attrname == key :
+                    attribute[j][1].append(value)
+                    return
+            attribute.append((key, [value]))        
+            
+    def __getitem__(self, key) :
+        """Returns an attribute's value."""
+        answer = []
+        attributeslist = getattr(self.request, "_%s_attributes" % self.name)
+        for i in range(len(attributeslist)) :
+            attribute = attributeslist[i]
+            for j in range(len(attribute)) :
+                (attrname, attrvalue) = attribute[j]
+                if attrname == key :
+                    answer.extend(attrvalue)
+        if answer :
+            return answer
+        raise KeyError, key            
+    
+class IPPRequest :
+    """A class for IPP requests."""
+    attributes_types = ("operation", "job", "printer", "unsupported", \
+                                     "subscription", "event_notification")
+    def __init__(self, data="", version=IPP_VERSION, 
+                                operation_id=None, \
+                                request_id=None, \
+                                debug=False) :
+        """Initializes an IPP Message object.
+        
+           Parameters :
+           
+             data : the complete IPP Message's content.
+             debug : a boolean value to output debug info on stderr.
+        """
+        self.debug = debug
+        self._data = data
+        self.parsed = False
+        
+        # Initializes message
+        self.setVersion(version)                
+        self.setOperationId(operation_id)
+        self.setRequestId(request_id)
+        self.data = ""
+        
+        for attrtype in self.attributes_types :
+            setattr(self, "_%s_attributes" % attrtype, [[]])
+        
+        # Initialize tags    
+        self.tags = [ None ] * 256 # by default all tags reserved
+        
+        # Delimiter tags
+        self.tags[0x01] = "operation-attributes-tag"
+        self.tags[0x02] = "job-attributes-tag"
+        self.tags[0x03] = "end-of-attributes-tag"
+        self.tags[0x04] = "printer-attributes-tag"
+        self.tags[0x05] = "unsupported-attributes-tag"
+        self.tags[0x06] = "subscription-attributes-tag"
+        self.tags[0x07] = "event_notification-attributes-tag"
+        
+        # out of band values
+        self.tags[0x10] = "unsupported"
+        self.tags[0x11] = "reserved-for-future-default"
+        self.tags[0x12] = "unknown"
+        self.tags[0x13] = "no-value"
+        self.tags[0x15] = "not-settable"
+        self.tags[0x16] = "delete-attribute"
+        self.tags[0x17] = "admin-define"
+  
+        # integer values
+        self.tags[0x20] = "generic-integer"
+        self.tags[0x21] = "integer"
+        self.tags[0x22] = "boolean"
+        self.tags[0x23] = "enum"
+        
+        # octetString
+        self.tags[0x30] = "octetString-with-an-unspecified-format"
+        self.tags[0x31] = "dateTime"
+        self.tags[0x32] = "resolution"
+        self.tags[0x33] = "rangeOfInteger"
+        self.tags[0x34] = "begCollection" # TODO : find sample files for testing
+        self.tags[0x35] = "textWithLanguage"
+        self.tags[0x36] = "nameWithLanguage"
+        self.tags[0x37] = "endCollection"
+        
+        # character strings
+        self.tags[0x40] = "generic-character-string"
+        self.tags[0x41] = "textWithoutLanguage"
+        self.tags[0x42] = "nameWithoutLanguage"
+        self.tags[0x44] = "keyword"
+        self.tags[0x45] = "uri"
+        self.tags[0x46] = "uriScheme"
+        self.tags[0x47] = "charset"
+        self.tags[0x48] = "naturalLanguage"
+        self.tags[0x49] = "mimeMediaType"
+        self.tags[0x4a] = "memberAttrName"
+        
+        # Reverse mapping to generate IPP messages
+        self.tagvalues = {}
+        for i in range(len(self.tags)) :
+            value = self.tags[i]
+            if value is not None :
+                self.tagvalues[value] = i
+                                     
+    def __getattr__(self, name) :                                 
+        """Fakes attribute access."""
+        if name in self.attributes_types :
+            return FakeAttribute(self, name)
+        else :
+            raise AttributeError, name
+            
+    def __str__(self) :        
+        """Returns the parsed IPP message in a readable form."""
+        if not self.parsed :
+            return ""
+        mybuffer = []
+        mybuffer.append("IPP version : %s.%s" % self.version)
+        mybuffer.append("IPP operation Id : 0x%04x" % self.operation_id)
+        mybuffer.append("IPP request Id : 0x%08x" % self.request_id)
+        for attrtype in self.attributes_types :
+            for attribute in getattr(self, "_%s_attributes" % attrtype) :
+                if attribute :
+                    mybuffer.append("%s attributes :" % attrtype.title())
+                for (name, value) in attribute :
+                    mybuffer.append("  %s : %s" % (name, value))
+        if self.data :            
+            mybuffer.append("IPP datas : %s" % repr(self.data))
+        return "\n".join(mybuffer)
+        
+    def logDebug(self, msg) :    
+        """Prints a debug message."""
+        if self.debug :
+            sys.stderr.write("%s\n" % msg)
+            sys.stderr.flush()
+            
+    def setVersion(self, version) :
+        """Sets the request's operation id."""
+        if version is not None :
+            try :
+                self.version = [int(p) for p in version.split(".")]
+            except AttributeError :
+                if len(version) == 2 : # 2-tuple
+                    self.version = version
+                else :    
+                    try :
+                        self.version = [int(p) for p in str(float(version)).split(".")]
+                    except :
+                        self.version = [int(p) for p in IPP_VERSION.split(".")]
+        
+    def setOperationId(self, opid) :        
+        """Sets the request's operation id."""
+        self.operation_id = opid
+        
+    def setRequestId(self, reqid) :        
+        """Sets the request's request id."""
+        self.request_id = reqid
+        
+    def dump(self) :    
+        """Generates an IPP Message.
+        
+           Returns the message as a string of text.
+        """    
+        mybuffer = []
+        if None not in (self.version, self.operation_id) :
+            mybuffer.append(chr(self.version[0]) + chr(self.version[1]))
+            mybuffer.append(pack(">H", self.operation_id))
+            mybuffer.append(pack(">I", self.request_id or 1))
+            for attrtype in self.attributes_types :
+                for attribute in getattr(self, "_%s_attributes" % attrtype) :
+                    if attribute :
+                        mybuffer.append(chr(self.tagvalues["%s-attributes-tag" % attrtype]))
+                    for (attrname, value) in attribute :
+                        nameprinted = 0
+                        for (vtype, val) in value :
+                            mybuffer.append(chr(self.tagvalues[vtype]))
+                            if not nameprinted :
+                                mybuffer.append(pack(">H", len(attrname)))
+                                mybuffer.append(attrname)
+                                nameprinted = 1
+                            else :     
+                                mybuffer.append(pack(">H", 0))
+                            if vtype in ("integer", "enum") :
+                                mybuffer.append(pack(">H", 4))
+                                mybuffer.append(pack(">I", val))
+                            elif vtype == "boolean" :
+                                mybuffer.append(pack(">H", 1))
+                                mybuffer.append(chr(val))
+                            else :    
+                                mybuffer.append(pack(">H", len(val)))
+                                mybuffer.append(val)
+            mybuffer.append(chr(self.tagvalues["end-of-attributes-tag"]))
+        mybuffer.append(self.data)    
+        return "".join(mybuffer)
+            
+    def parse(self) :
+        """Parses an IPP Request.
+        
+           NB : Only a subset of RFC2910 is implemented.
+        """
+        self._curname = None
+        self._curattributes = None
+        
+        self.setVersion((ord(self._data[0]), ord(self._data[1])))
+        self.setOperationId(unpack(">H", self._data[2:4])[0])
+        self.setRequestId(unpack(">I", self._data[4:8])[0])
+        self.position = 8
+        endofattributes = self.tagvalues["end-of-attributes-tag"]
+        maxdelimiter = self.tagvalues["event_notification-attributes-tag"]
+        nulloffset = lambda : 0
+        try :
+            tag = ord(self._data[self.position])
+            while tag != endofattributes :
+                self.position += 1
+                name = self.tags[tag]
+                if name is not None :
+                    func = getattr(self, name.replace("-", "_"), nulloffset)
+                    self.position += func()
+                    if ord(self._data[self.position]) > maxdelimiter :
+                        self.position -= 1
+                        continue
+                oldtag = tag        
+                tag = ord(self._data[self.position])
+                if tag == oldtag :
+                    self._curattributes.append([])
+        except IndexError :
+            raise IPPError, "Unexpected end of IPP message."
+            
+        self.data = self._data[self.position+1:]            
+        self.parsed = True
+        
+    def parseTag(self) :    
+        """Extracts information from an IPP tag."""
+        pos = self.position
+        tagtype = self.tags[ord(self._data[pos])]
+        pos += 1
+        posend = pos2 = pos + 2
+        namelength = unpack(">H", self._data[pos:pos2])[0]
+        if not namelength :
+            name = self._curname
+        else :    
+            posend += namelength
+            self._curname = name = self._data[pos2:posend]
+        pos2 = posend + 2
+        valuelength = unpack(">H", self._data[posend:pos2])[0]
+        posend = pos2 + valuelength
+        value = self._data[pos2:posend]
+        if tagtype in ("integer", "enum") :
+            value = unpack(">I", value)[0]
+        elif tagtype == "boolean" :    
+            value = ord(value)
+        try :    
+            (oldname, oldval) = self._curattributes[-1][-1]
+            if oldname == name :
+                oldval.append((tagtype, value))
+            else :    
+                raise IndexError
+        except IndexError :    
+            self._curattributes[-1].append((name, [(tagtype, value)]))
+        self.logDebug("%s(%s) : %s" % (name, tagtype, value))
+        return posend - self.position
+        
+    def operation_attributes_tag(self) : 
+        """Indicates that the parser enters into an operation-attributes-tag group."""
+        self._curattributes = self._operation_attributes
+        return self.parseTag()
+        
+    def job_attributes_tag(self) : 
+        """Indicates that the parser enters into a job-attributes-tag group."""
+        self._curattributes = self._job_attributes
+        return self.parseTag()
+        
+    def printer_attributes_tag(self) : 
+        """Indicates that the parser enters into a printer-attributes-tag group."""
+        self._curattributes = self._printer_attributes
+        return self.parseTag()
+        
+    def unsupported_attributes_tag(self) : 
+        """Indicates that the parser enters into an unsupported-attributes-tag group."""
+        self._curattributes = self._unsupported_attributes
+        return self.parseTag()
+        
+    def subscription_attributes_tag(self) : 
+        """Indicates that the parser enters into a subscription-attributes-tag group."""
+        self._curattributes = self._subscription_attributes
+        return self.parseTag()
+        
+    def event_notification_attributes_tag(self) : 
+        """Indicates that the parser enters into an event-notification-attributes-tag group."""
+        self._curattributes = self._event_notification_attributes
+        return self.parseTag()
+        
+            
+class CUPS :
+    """A class for a CUPS instance."""
+    def __init__(self, url=None, username=None, password=None, charset="utf-8", language="en-us", debug=False) :
+        """Initializes the CUPS instance."""
+        if url is not None :
+            self.url = url.replace("ipp://", "http://")
+            if self.url.endswith("/") :
+                self.url = self.url[:-1]
+        else :        
+            self.url = self.getDefaultURL()
+        self.username = username
+        self.password = password
+        self.charset = charset
+        self.language = language
+        self.debug = debug
+        self.lastError = None
+        self.lastErrorMessage = None
+        self.requestId = None
+        
+    def getDefaultURL(self) :    
+        """Builds a default URL."""
+        # TODO : encryption methods.
+        server = os.environ.get("CUPS_SERVER") or "localhost"
+        port = os.environ.get("IPP_PORT") or 631
+        if server.startswith("/") :
+            # it seems it's a unix domain socket.
+            # we can't handle this right now, so we use the default instead.
+            return "http://localhost:%s" % port
+        else :    
+            return "http://%s:%s" % (server, port)
+            
+    def identifierToURI(self, service, ident) :
+        """Transforms an identifier into a particular URI depending on requested service."""
+        return "%s/%s/%s" % (self.url.replace("http://", "ipp://"),
+                             service,
+                             ident)
+        
+    def nextRequestId(self) :        
+        """Increments the current request id and returns the new value."""
+        try :
+            self.requestId += 1
+        except TypeError :    
+            self.requestId = 1
+        return self.requestId
+            
+    def newRequest(self, operationid=None) :
+        """Generates a new empty request."""
+        if operationid is not None :
+            req = IPPRequest(operation_id=operationid, \
+                             request_id=self.nextRequestId(), \
+                             debug=self.debug)
+            req.operation["attributes-charset"] = ("charset", self.charset)
+            req.operation["attributes-natural-language"] = ("naturalLanguage", self.language)
+            return req
+    
+    def doRequest(self, req, url=None) :
+        """Sends a request to the CUPS server.
+           returns a new IPPRequest object, containing the parsed answer.
+        """   
+        connexion = urllib2.Request(url=url or self.url, \
+                             data=req.dump())
+        connexion.add_header("Content-Type", "application/ipp")
+        if self.username :
+            pwmanager = urllib2.HTTPPasswordMgrWithDefaultRealm()
+            pwmanager.add_password(None, \
+                                   "%s%s" % (connexion.get_host(), connexion.get_selector()), \
+                                   self.username, \
+                                   self.password or "")
+            authhandler = urllib2.HTTPBasicAuthHandler(pwmanager)                       
+            opener = urllib2.build_opener(authhandler)
+            urllib2.install_opener(opener)
+        self.lastError = None    
+        self.lastErrorMessage = None
+        try :    
+            response = urllib2.urlopen(connexion)
+        except (urllib2.URLError, urllib2.HTTPError, socket.error), error :    
+            self.lastError = error
+            self.lastErrorMessage = str(error)
+            return None
+        else :    
+            datas = response.read()
+            ippresponse = IPPRequest(datas)
+            ippresponse.parse()
+            return ippresponse
+    
+    def getPPD(self, queuename) :    
+        """Retrieves the PPD for a particular queuename."""
+        req = self.newRequest(IPP_GET_PRINTER_ATTRIBUTES)
+        req.operation["printer-uri"] = ("uri", self.identifierToURI("printers", queuename))
+        for attrib in ("printer-uri-supported", "printer-type", "member-uris") :
+            req.operation["requested-attributes"] = ("nameWithoutLanguage", attrib)
+        return self.doRequest(req)  # TODO : get the PPD from the actual print server
+        
+    def getDefault(self) :
+        """Retrieves CUPS' default printer."""
+        return self.doRequest(self.newRequest(CUPS_GET_DEFAULT))
+    
+    def getJobAttributes(self, jobid) :    
+        """Retrieves a print job's attributes."""
+        req = self.newRequest(IPP_GET_JOB_ATTRIBUTES)
+        req.operation["job-uri"] = ("uri", self.identifierToURI("jobs", jobid))
+        return self.doRequest(req)
+        
+    def getPrinters(self) :    
+        """Returns the list of print queues names."""
+        req = self.newRequest(CUPS_GET_PRINTERS)
+        req.operation["requested-attributes"] = ("keyword", "printer-name")
+        req.operation["printer-type"] = ("enum", 0)
+        req.operation["printer-type-mask"] = ("enum", CUPS_PRINTER_CLASS)
+        return [printer[1] for printer in self.doRequest(req).printer["printer-name"]]
+        
+    def getDevices(self) :    
+        """Returns a list of devices as (deviceclass, deviceinfo, devicemakeandmodel, deviceuri) tuples."""
+        answer = self.doRequest(self.newRequest(CUPS_GET_DEVICES))
+        return zip([d[1] for d in answer.printer["device-class"]], \
+                   [d[1] for d in answer.printer["device-info"]], \
+                   [d[1] for d in answer.printer["device-make-and-model"]], \
+                   [d[1] for d in answer.printer["device-uri"]])
+                   
+    def getPPDs(self) :    
+        """Returns a list of PPDs as (ppdnaturallanguage, ppdmake, ppdmakeandmodel, ppdname) tuples."""
+        answer = self.doRequest(self.newRequest(CUPS_GET_PPDS))
+        return zip([d[1] for d in answer.printer["ppd-natural-language"]], \
+                   [d[1] for d in answer.printer["ppd-make"]], \
+                   [d[1] for d in answer.printer["ppd-make-and-model"]], \
+                   [d[1] for d in answer.printer["ppd-name"]])
+                   
+    def createSubscription(self, uri, events=["all"],
+                                      userdata=None,
+                                      recipient=None,
+                                      pullmethod=None,
+                                      charset=None,
+                                      naturallanguage=None,
+                                      leaseduration=None,
+                                      timeinterval=None,
+                                      jobid=None) :
+        """Creates a job, printer or server subscription.
+         
+           uri : the subscription's uri, e.g. ipp://server
+           events : a list of events to subscribe to, e.g. ["printer-added", "printer-deleted"]
+           recipient : the notifier's uri
+           pullmethod : the pull method to use
+           charset : the charset to use when sending notifications
+           naturallanguage : the language to use when sending notifications
+           leaseduration : the duration of the lease in seconds
+           timeinterval : the interval of time during notifications
+           jobid : the optional job id in case of a job subscription
+        """   
+        if jobid is not None :
+            opid = IPP_CREATE_JOB_SUBSCRIPTION
+            uritype = "job-uri"
+        else :
+            opid = IPP_CREATE_PRINTER_SUBSCRIPTION
+            uritype = "printer-uri"
+        req = self.newRequest(opid)
+        req.operation[uritype] = ("uri", uri)
+        for event in events :
+            req.subscription["notify-events"] = ("keyword", event)
+        if userdata is not None :    
+            req.subscription["notify-user-data"] = ("octetString-with-an-unspecified-format", userdata)
+        if recipient is not None :    
+            req.subscription["notify-recipient"] = ("uri", recipient)
+        if pullmethod is not None :
+            req.subscription["notify-pull-method"] = ("keyword", pullmethod)
+        if charset is not None :
+            req.subscription["notify-charset"] = ("charset", charset)
+        if naturallanguage is not None :
+            req.subscription["notify-natural-language"] = ("naturalLanguage", naturallanguage)
+        if leaseduration is not None :
+            req.subscription["notify-lease-duration"] = ("integer", leaseduration)
+        if timeinterval is not None :
+            req.subscription["notify-time-interval"] = ("integer", timeinterval)
+        if jobid is not None :
+            req.subscription["notify-job-id"] = ("integer", jobid)
+        return self.doRequest(req)
+            
+    def cancelSubscription(self, uri, subscriptionid, jobid=None) :    
+        """Cancels a subscription.
+        
+           uri : the subscription's uri.
+           subscriptionid : the subscription's id.
+           jobid : the optional job's id.
+        """
+        req = self.newRequest(IPP_CANCEL_SUBSCRIPTION)
+        if jobid is not None :
+            uritype = "job-uri"
+        else :
+            uritype = "printer-uri"
+        req.operation[uritype] = ("uri", uri)
+        req.event_notification["notify-subscription-id"] = ("integer", subscriptionid)
+        return self.doRequest(req)
+        
+if __name__ == "__main__" :            
+    if (len(sys.argv) < 2) or (sys.argv[1] == "--debug") :
+        print "usage : python pkipplib.py /var/spool/cups/c00005 [--debug] (for example)\n"
+    else :    
+        infile = open(sys.argv[1], "rb")
+        filedata = infile.read()
+        infile.close()
+        
+        msg = IPPRequest(filedata, debug=(sys.argv[-1]=="--debug"))
+        msg.parse()
+        msg2 = IPPRequest(msg.dump())
+        msg2.parse()
+        filedata2 = msg2.dump()
+        
+        if filedata == filedata2 :
+            print "Test OK : parsing original and parsing the output of the dump produce the same dump !"
+            print str(msg)
+        else :    
+            print "Test Failed !"
+            print str(msg)
+            print
+            print str(msg2)
+        
Index: /pkipplib/tags/0.07/pkipplib/__init__.py
===================================================================
--- /pkipplib/tags/0.07/pkipplib/__init__.py (revision 14)
+++ /pkipplib/tags/0.07/pkipplib/__init__.py (revision 14)
@@ -0,0 +1,21 @@
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP support for Python
+#
+# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
Index: /pkipplib/tags/0.07/pkipplib/version.py
===================================================================
--- /pkipplib/tags/0.07/pkipplib/version.py (revision 27)
+++ /pkipplib/tags/0.07/pkipplib/version.py (revision 27)
@@ -0,0 +1,43 @@
+# -*- coding: ISO-8859-15 -*-
+#
+# pkipplib : IPP support for Python
+#
+# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
+
+__version__ = "0.07"
+
+__doc__ = "pkipplib : IPP and CUPS support for Python."
+
+__author__ = "Jerome Alet - alet@librelogiciel.com"
+
+__years__ = "2003, 2004, 2005, 2006"
+
+__gplblurb__ = """This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA."""
Index: /pkipplib/tags/0.07/MANIFEST.in
===================================================================
--- /pkipplib/tags/0.07/MANIFEST.in (revision 36)
+++ /pkipplib/tags/0.07/MANIFEST.in (revision 36)
@@ -0,0 +1,2 @@
+include README COPYING NEWS MANIFEST.in clean.sh bin/pksubscribe
+recursive-include notifiers *
Index: /pkipplib/tags/0.07/clean.sh
===================================================================
--- /pkipplib/tags/0.07/clean.sh (revision 16)
+++ /pkipplib/tags/0.07/clean.sh (revision 16)
@@ -0,0 +1,30 @@
+#! /bin/sh
+#
+# pkipplib : IPP support for Python
+#
+# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+
+# Use this to clean the tree from temporary files
+
+rm -f MANIFEST
+rm -fr build dist
+find . -name "*.bak" -exec rm -f {} \;
+find . -name "*~" -exec rm -f {} \;
+find . -name "*.pyc" -exec rm -f {} \;
+find . -name "*.pyo" -exec rm -f {} \;
+find . -name "*.jem" -exec rm -f {} \;
Index: /pkipplib/tags/0.07/README
===================================================================
--- /pkipplib/tags/0.07/README (revision 35)
+++ /pkipplib/tags/0.07/README (revision 35)
@@ -0,0 +1,170 @@
+pkipplib : IPP and CUPS support for Python
+
+$Id$
+
+(c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+=======================================================================
+
+This software is a Python library which can prepare IPP requests with
+the help of a somewhat high level API. These requests can then be sent
+to an IPP printer or print server (e.g. CUPS).
+
+This library can also parse IPP answers received, and create high
+level Python objects from them.
+
+Both of these actions can be done through an IPPRequest class
+and its instance methods.
+
+Finally, a CUPS class can be leveraged to easily deal with CUPS 
+print servers.
+
+All of this library being written in the Python language, there's
+no need to link the code with the CUPS' API, which makes it
+independant of the CUPS version being installed.
+
+=======================================================================
+
+Installation :
+--------------
+
+        1 - Grab the latest version of this library from :
+        
+            http://www.pykota.com/software/pkipplib/
+            
+        2 - Extract the tarball :    
+        
+            $ tar -zxf pkipplib-x.yy.tar.gz
+            
+        3 - Install the Python module :    
+        
+            $ cd pkipplib-x.yy
+            $ python setup.py install
+
+NB : for the installation script to work, you need to install
+the python-dev package on your system first.
+
+=======================================================================
+
+Examples :
+----------
+
+--- CUT ---
+# Parsing.py    EXAMPLE
+from pkipplib import pkipplib
+
+# Read IPP datas from a CUPS job control file
+myfile = open("/var/spool/cups/c00155")
+ippdatas = myfile.read()
+myfile.close()
+
+# Parse these datas
+request = pkipplib.IPPRequest(ippdatas)
+request.parse()
+
+# Print the whole result as a string
+print request
+
+# Access one of the job's attributes directly 
+print request.job["job-name"]
+--- CUT ---
+
+
+--- CUT ---
+# Building.py   EXAMPLE
+from pkipplib import pkipplib
+
+# Create a CUPS_GET_DEFAULT request
+request = pkipplib.IPPRequest(operation_id=pkipplib.CUPS_GET_DEFAULT)
+request.operation["attributes-charset"] = ("charset", "utf-8")
+request.operation["attributes-natural-language"] = ("naturalLanguage", "en-us")
+
+# Get the request as binary datas
+ippdatas = request.dump()
+
+# Parse these datas back
+newrequest = pkipplib.IPPRequest(ippdatas)
+newrequest.parse()
+
+# Of course, the result of parsing matches what we created before.
+print newrequest.operation["attributes-natural-language"]
+--- CUT ---
+
+
+--- CUT ---
+# CUPS' API
+from pkipplib import pkipplib
+
+# Create a CUPS client instance
+# cups = pkipplib.CUPS(url="http://server:631", username="john", password="blah!")
+cups = pkipplib.CUPS()
+
+# High level API : retrieve info about job 3 :
+answer = cups.getJobAttributes(3)
+print answer.job["document-format"]
+# That's all folks !
+
+# Lower level API :
+request = cups.newRequest(pkipplib.IPP_GET_PRINTER_ATTRIBUTES)
+request.operation["printer-uri"] = ("uri", 
+                                    cups.identifierToURI("printers", "HP2100"))
+for attribute in ("printer-uri-supported",                                     
+                  "printer-type",
+                  "member-uris") :
+    # IMPORTANT : here, despite the unusual syntax, we append to              
+    # the list of requested attributes :
+    request.operation["requested-attributes"] = ("nameWithoutLanguage", attribute)
+    
+# Sends this request to the CUPS server    
+answer = cups.doRequest(request)
+
+# Print the answer as a string of text
+print answer
+--- CUT ---
+
+=======================================================================
+
+Command line tools :
+
+  pkipplib currently includes the following command line tools :
+  
+    * pksubscribe : can create or delete IPP subscriptions.
+    
+          See pksubscribe --help for details.
+        
+          examples :
+          
+            $ pksubscribe --cups http://localhost:631 \
+                          --events printer-added,printer-deleted \
+                          --recipient mynotifier \
+                          --duration 0
+                          
+            $ pksubscribe --username root \              
+                          --password HacKMe \
+                          --delete 34 58 98
+                          
+    * samplenotifier : can handle printer-added and printer-deleted
+      notifications and automatically create or remove printers
+      from PyKota's database (just adapt the code for other needs).
+      
+      samplenotifier is present in the notifiers/ directory, but
+      won't be installed automatically.
+
+  In the future more command line tools will be included.
+  
+=======================================================================
+
+Please email bug reports, enhancements or comments to : alet@librelogiciel.com
