Skip to content

/ Zope / gocept svn checkins / Archive / 2008 / 2008-07 / SVN: r6038 - gocept.cvs/tags/0.1.9

[ << ] [ >> ]

[ SVN: r6030 - CMFLinkChecker/branches/2.1 / ... ] [ SVN: r6043 - in CMFLinkChecker/trunk: . ... ]

SVN: r6038 - gocept.cvs/tags/0.1.9
Michael Howitz <mh(at)gocept.com>
2008-07-14 15:01:48 [ FULL ]
Author: mac
Date: Mon Jul 14 15:01:48 2008
New Revision: 6038

Log:
releasing 0.1.9


Added:
   gocept.cvs/tags/0.1.9/
      - copied from r6037, gocept.cvs/trunk/

SVN: r6039 - gocept.cvs/tags/0.1.9
Michael Howitz <mh(at)gocept.com>
2008-07-14 15:03:11 [ FULL ]
Author: mac
Date: Mon Jul 14 15:03:10 2008
New Revision: 6039

Log:
removed dev


Modified:
   gocept.cvs/tags/0.1.9/setup.py

Modified: gocept.cvs/tags/0.1.9/setup.py
==============================================================================
--- gocept.cvs/tags/0.1.9/setup.py	(original)
+++ gocept.cvs/tags/0.1.9/setup.py	Mon Jul 14 15:03:10 2008
(at)(at) -3,7 +3,7 (at)(at)
 name = "gocept.cvs"
 setup(
     name = name,
-    version = "0.1.9dev",
+    version = "0.1.9",
     author = "Daniel Havlik",
     author_email = "dh(at)gocept.com",
     description = "zc.buildout recipe for checking out cvs modules.",

SVN: r6040 - gocept.cvs/trunk
Michael Howitz <mh(at)gocept.com>
2008-07-14 15:06:12 [ FULL ]
Author: mac
Date: Mon Jul 14 15:06:11 2008
New Revision: 6040

Log:
0.1.9 is history


Modified:
   gocept.cvs/trunk/setup.py

Modified: gocept.cvs/trunk/setup.py
==============================================================================
--- gocept.cvs/trunk/setup.py	(original)
+++ gocept.cvs/trunk/setup.py	Mon Jul 14 15:06:11 2008
(at)(at) -3,7 +3,7 (at)(at)
 name = "gocept.cvs"
 setup(
     name = name,
-    version = "0.1.9dev",
+    version = "0.1.10dev",
     author = "Daniel Havlik",
     author_email = "dh(at)gocept.com",
     description = "zc.buildout recipe for checking out cvs modules.",

SVN: r6057 - gocept.cvs/tags/0.1.10
Michael Howitz <mh(at)gocept.com>
2008-07-15 09:26:20 [ FULL ]
Author: mac
Date: Tue Jul 15 09:26:19 2008
New Revision: 6057

Log:
releasing 0.1.10


Added:
   gocept.cvs/tags/0.1.10/
      - copied from r6056, gocept.cvs/trunk/

SVN: r6058 - gocept.cvs/tags/0.1.10
Michael Howitz <mh(at)gocept.com>
2008-07-15 09:27:41 [ FULL ]
Author: mac
Date: Tue Jul 15 09:27:40 2008
New Revision: 6058

Log:
removing dev


Modified:
   gocept.cvs/tags/0.1.10/setup.py

Modified: gocept.cvs/tags/0.1.10/setup.py
==============================================================================
--- gocept.cvs/tags/0.1.10/setup.py	(original)
+++ gocept.cvs/tags/0.1.10/setup.py	Tue Jul 15 09:27:40 2008
(at)(at) -3,7 +3,7 (at)(at)
 name = "gocept.cvs"
 setup(
     name = name,
-    version = "0.1.10dev",
+    version = "0.1.10",
     author = "Daniel Havlik",
     author_email = "dh(at)gocept.com",
     description = "zc.buildout recipe for checking out cvs modules.",

SVN: r6059 - gocept.cvs/trunk
Michael Howitz <mh(at)gocept.com>
2008-07-15 09:28:31 [ FULL ]
Author: mac
Date: Tue Jul 15 09:28:30 2008
New Revision: 6059

Log:
0.1.10 is history


Modified:
   gocept.cvs/trunk/setup.py

Modified: gocept.cvs/trunk/setup.py
==============================================================================
--- gocept.cvs/trunk/setup.py	(original)
+++ gocept.cvs/trunk/setup.py	Tue Jul 15 09:28:30 2008
(at)(at) -3,7 +3,7 (at)(at)
 name = "gocept.cvs"
 setup(
     name = name,
-    version = "0.1.10dev",
+    version = "0.1.11dev",
     author = "Daniel Havlik",
     author_email = "dh(at)gocept.com",
     description = "zc.buildout recipe for checking out cvs modules.",

SVN: r6078 - gocept.lms/trunk/src/gocept/lms
Christian Zagrodnick <cz(at)gocept.com>
2008-07-15 16:03:36 [ FULL ]
Author: zagy
Date: Tue Jul 15 16:03:35 2008
New Revision: 6078

Log:
documentation


Modified:
   gocept.lms/trunk/src/gocept/lms/schedule.txt

Modified: gocept.lms/trunk/src/gocept/lms/schedule.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/schedule.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/schedule.txt	Tue Jul 15 16:03:35 2008
(at)(at) -2,28 +2,37 (at)(at)
 The scheduler
 =============
 
-[#functionaltest]_
+The scheduler looks over the URLs in the database and decides which one have
to
+be checked[#functionaltest]_.
+
+Register some URLs:
 
 >>> import zope.component
 >>> import gocept.lms.interfaces
-
 >>> urls =
zope.component.getUtility(gocept.lms.interfaces.IURLProvider)
-
 >>> url1 = urls.add('http://example.com/1')
 >>> url2 = urls.add('http://example.com/2')
 >>> url3 = urls.add('http://example.com/3')
 >>> url4 = urls.add('http://example.com/4')
 
+Set different states:
+
 >>> url1.state = gocept.lms.interfaces.STATE_OK
 >>> url2.state = gocept.lms.interfaces.STATE_TEMPORARY
 >>> url3.state = gocept.lms.interfaces.STATE_UNAVAILABLE
 
+The scheduler puts the URLs to be checked into the check queue. Initially it
is
+empty:
+
 >>> import zc.queue.interfaces
 >>> check_queue =
zope.component.getUtility(zc.queue.interfaces.IQueue,
 ...                                         name='check')
 >>> list(check_queue)
 []
 
+Run the scheduler. None of the created URLs have been checked so the queue
will
+contain all URLs after the scheduling run:
+
 >>> import gocept.lms.schedule
 >>> gocept.lms.schedule.schedule()
 >>> list(check_queue)
(at)(at) -33,6 +42,8 (at)(at)
  <gocept.lms.url.URL 'http://example.com/4'>]
 
 
+
+
 .. [#functionaltest] Setup functional test
 
     >>> import gocept.lms.app

SVN: r6080 - gocept.lms/trunk/src/gocept/lms
Christian Zagrodnick <cz(at)gocept.com>
2008-07-15 16:51:43 [ FULL ]
Author: zagy
Date: Tue Jul 15 16:51:42 2008
New Revision: 6080

Log:
more tests


Modified:
   gocept.lms/trunk/src/gocept/lms/schedule.txt

Modified: gocept.lms/trunk/src/gocept/lms/schedule.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/schedule.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/schedule.txt	Tue Jul 15 16:51:42 2008
(at)(at) -42,6 +42,110 (at)(at)
  <gocept.lms.url.URL 'http://example.com/4'>]
 
 
+When we run the scheduler again we'll have every link twice in the queue:
+
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[<gocept.lms.url.URL 'http://example.com/1'>,
+ <gocept.lms.url.URL 'http://example.com/2'>,
+ <gocept.lms.url.URL 'http://example.com/3'>,
+ <gocept.lms.url.URL 'http://example.com/4'>,
+ <gocept.lms.url.URL 'http://example.com/1'>,
+ <gocept.lms.url.URL 'http://example.com/2'>,
+ <gocept.lms.url.URL 'http://example.com/3'>,
+ <gocept.lms.url.URL 'http://example.com/4'>]
+
+
+Let's empty the queue and set last_check dates:
+
+>>> while check_queue:
+...     check_queue.pull()
+<gocept.lms.url.URL 'http://example.com/1'>
+<gocept.lms.url.URL 'http://example.com/2'>
+<gocept.lms.url.URL 'http://example.com/3'>
+<gocept.lms.url.URL 'http://example.com/4'>
+<gocept.lms.url.URL 'http://example.com/1'>
+<gocept.lms.url.URL 'http://example.com/2'>
+<gocept.lms.url.URL 'http://example.com/3'>
+<gocept.lms.url.URL 'http://example.com/4'>
+>>> list(check_queue)
+[]
+
+>>> import datetime
+>>> import pytz
+>>> now = datetime.datetime.now(pytz.UTC)
+>>> second = datetime.timedelta(seconds=1)
+>>> url1.last_check = now
+>>> url2.last_check = now + second
+>>> url3.last_check = now + 2*second
+>>> url4.last_check = now + 3*second
+
+Since we're using a catalog we need to send ObjectModified events:
+
+>>> import zope.event
+>>> import zope.lifecycleevent
+>>> zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url1))
+>>> zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url2))
+>>> zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url3))
+>>> zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url4))
+
+When we schedule now, nothing will be added to the check queue:
+
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[]
+
+Lower the check interval to one second:
+
+>>> gocept.lms.schedule.INTERVAL = datetime.timedelta(seconds=1)
+
+Let's wait a second and schedule again:
+
+>>> import time
+>>> time.sleep(1)
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[<gocept.lms.url.URL 'http://example.com/1'>]
+
+"check" the entire queue:
+
+>>> def check_all():
+...     while check_queue:
+...         url = check_queue.pull()
+...         url.last_check = datetime.datetime.now(pytz.UTC)
+...         zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url))
+>>> check_all()
+
+When we schedule now, nothing will be put to the queue again:
+
+
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[]
+
+
+Let's wait a second and schedule again. Now 1 and 2 are to be checked:
+
+>>> import time
+>>> time.sleep(1)
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[<gocept.lms.url.URL 'http://example.com/1'>,
+ <gocept.lms.url.URL 'http://example.com/2'>]
+
+"check" the queue:
+
+>>> check_all()
+
+When we schedule now, nothing will be put to the queue again:
+
+>>> gocept.lms.schedule.schedule()
+>>> list(check_queue)
+[]
+
+
+
+
 
 
 .. [#functionaltest] Setup functional test

SVN: r6083 - gocept.lms/trunk/src/gocept/lms
Christian Theune <ct(at)gocept.com>
2008-07-15 18:15:30 [ FULL ]
Author: ctheune
Date: Tue Jul 15 18:15:29 2008
New Revision: 6083

Log:
codify edge case and why it is ok for us



Modified:
   gocept.lms/trunk/src/gocept/lms/notify.txt

Modified: gocept.lms/trunk/src/gocept/lms/notify.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/notify.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/notify.txt	Tue Jul 15 18:15:29 2008
(at)(at) -52,20 +52,19 (at)(at)
 Connect: None
 Update many states: fred - 3
 
-# XXX solve this case
-#When Fred subscribes to another URL he will be notified about its state:
-#
-#>>> fred.register_urls([url4])
-#>>> notify()
-#>>> xmlrpc.show_log()
-#Connect: None
-#Update many states: fred - 1
-
 Running another notification would not produce any actions, because Fred was
successfully notified:
 
 >>> notify()
 >>> xmlrpc.show_log()
 
+When Fred subscribes to another URL that has a `last state change` date
+befor his last notification he will not receive a notification from this
+component (those status updates are handled by the XML-RPC interface):
+
+>>> fred.register_urls([url4])
+>>> notify()
+>>> xmlrpc.show_log()
+
 .. [#functionaltest] Setup functional test
 
     >>> import gocept.lms.app

SVN: r6084 - gocept.lms/trunk/src/gocept/lms
Christian Theune <ct(at)gocept.com>
2008-07-16 09:07:28 [ FULL ]
Author: ctheune
Date: Wed Jul 16 09:07:26 2008
New Revision: 6084

Log:
Make set-/getClientNotifications functional.



Modified:
   gocept.lms/trunk/src/gocept/lms/app.py
   gocept.lms/trunk/src/gocept/lms/xmlrpc.txt

Modified: gocept.lms/trunk/src/gocept/lms/app.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/app.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/app.py	Wed Jul 16 09:07:26 2008
(at)(at) -121,13 +121,13 (at)(at)
     def checkConnection(self, client, password):
         return PROTOCOL_VERSION
 
-    def getClientNotifications(self, client_id, password):
-        # XXX API stub
-        return False
+    (at)authenticatedClient
+    def getClientNotifications(self, client, password):
+        return client.notify
 
-    def setClientNotifications(self, client_id, password, status):
-        # XXX API stub
-        pass
+    (at)authenticatedClient
+    def setClientNotifications(self, client, password, status):
+        client.notify = bool(status)
 
     (at)authenticatedClient
     def registerURLs(self, client, password, urls):

Modified: gocept.lms/trunk/src/gocept/lms/xmlrpc.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/xmlrpc.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/xmlrpc.txt	Wed Jul 16 09:07:26 2008
(at)(at) -122,13 +122,33 (at)(at)
 
 XXX what happens when invalid urls are registered?
 
+Setting/getting the notification status
+=======================================
 
-Various API methods, currently implemented as stubs
-===================================================
+Clients can choose whether or not hey want to retrieve notifications. This
+flag can be either set manually using the XML-RPC API or sometimes is set by
+the server (e.g. after a certain number of failed notifications):
 
+Initially, the notifications are sent:
+>>> server.getClientNotifications('gocept', password)
+True
+
+The client can choose to not receive them anymore:
+
+>>> server.setClientNotifications('gocept', password, False)
 >>> server.getClientNotifications('gocept', password)
 False
+
+And can re-enable them:
+
 >>> server.setClientNotifications('gocept', password, True)
+>>> server.getClientNotifications('gocept', password)
+True
+
+
+Various API methods, currently implemented as stubs
+===================================================
+
 >>> server.unregisterURLs('gocept', password, ['http://localhost'])
 >>> server.getInfoFrameURL('gocept', password)
 'http://localhost:8080/lms'

SVN: r6085 - gocept.lms/trunk/src/gocept/lms
Thomas Lotze <tl(at)gocept.com>
2008-07-16 09:50:53 [ FULL ]
Author: thomas
Date: Wed Jul 16 09:50:52 2008
New Revision: 6085

Log:
ensure that a client isn't notified too frequently, added tests


Modified:
   gocept.lms/trunk/src/gocept/lms/notify.py
   gocept.lms/trunk/src/gocept/lms/notify.txt

Modified: gocept.lms/trunk/src/gocept/lms/notify.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/notify.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/notify.py	Wed Jul 16 09:50:52 2008
(at)(at) -14,6 +14,10 (at)(at)
 
 import xmlrpclib
 
+
+INTERVAL = datetime.timedelta(seconds=5*60)
+
+
 # Provide hook for tests to set up dummies
 ServerProxy  = xmlrpclib.ServerProxy
 
(at)(at) -21,12 +25,17 (at)(at)
 def notify():
     url_query = zope.component.getUtility(hurry.query.interfaces.IQuery)
 
+    reference_time = datetime.datetime.now(pytz.UTC) - INTERVAL
+
     # Notify each client about URL changes that happened since the last
     # notification.
     clients = zope.component.getUtility(gocept.lms.interfaces.IClientProvider)
     for client in clients.values():
         if not client.notify:
             continue
+        if client.last_notification > reference_time:
+            # don't notify clients more frequently than every 5 minutes
+            continue
         urls = url_query.searchResults(
             Ge(('urls', 'last_state_change'), client.last_notification) &
             AnyOf(('urls', 'clients'), [client]))

Modified: gocept.lms/trunk/src/gocept/lms/notify.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/notify.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/notify.txt	Wed Jul 16 09:50:52 2008
(at)(at) -22,6 +22,12 (at)(at)
 ...       self.log = []
 >>> import gocept.lms.notify
 >>> gocept.lms.notify.ServerProxy = xmlrpc = XMLRPCDummy()
+>>> import datetime
+>>> gocept.lms.notify.INTERVAL = datetime.timedelta(seconds=0)
+
+
+Sending out notifications
+=========================
 
 When run with an empty database, nothing happens:
 
(at)(at) -52,19 +58,70 (at)(at)
 Connect: None
 Update many states: fred - 3
 
-Running another notification would not produce any actions, because Fred was
successfully notified:
+Running another notification would not produce any actions, because Fred was
+successfully notified:
 
 >>> notify()
 >>> xmlrpc.show_log()
 
 When Fred subscribes to another URL that has a `last state change` date
-befor his last notification he will not receive a notification from this
+before his last notification he will not receive a notification from this
 component (those status updates are handled by the XML-RPC interface):
 
 >>> fred.register_urls([url4])
 >>> notify()
 >>> xmlrpc.show_log()
 
+
+Suppressing notifications
+=========================
+
+There are two conditions for a client to not be notified. The first is if the
+client's notifications are disabled:
+
+>>> fred.last_notification = fred.__class__.last_notification
+>>> fred.notify = False
+>>> notify()
+>>> xmlrpc.show_log()
+
+The client's last notification date has not been updated because he didn't
+receive a notification:
+
+>>> fred.last_notification
+datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>)
+
+Clean-up:
+
+>>> fred.notify = True
+
+The second is if the client should receive notifications but has received one
+a short time ago:
+
+>>> import pytz
+>>> now = datetime.datetime.now(pytz.UTC)
+>>> fred.last_notification = now
+>>> gocept.lms.notify.INTERVAL = datetime.timedelta(seconds=2)
+
+Ensure that a URL was changed recently enough for the client to be notified
+about it:
+
+>>> url1.last_state_change = now
+>>> import zope.event, zope.lifecycleevent
+>>> zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url1))
+
+>>> notify()
+>>> xmlrpc.show_log()
+
+After the interval has elapsed, the client will receive notifications again:
+
+>>> import time
+>>> time.sleep(2.1)
+>>> notify()
+>>> xmlrpc.show_log()
+Connect: None
+Update many states: fred - 1
+
+
 .. [#functionaltest] Setup functional test
 
     >>> import gocept.lms.app

SVN: r6086 - gocept.lms/trunk/src/gocept/lms
Thomas Lotze <tl(at)gocept.com>
2008-07-16 10:26:21 [ FULL ]
Author: thomas
Date: Wed Jul 16 10:26:18 2008
New Revision: 6086

Log:
refactored client and notifications: better structure, more readability


Modified:
   gocept.lms/trunk/src/gocept/lms/app.py
   gocept.lms/trunk/src/gocept/lms/client.py
   gocept.lms/trunk/src/gocept/lms/interfaces.py
   gocept.lms/trunk/src/gocept/lms/notify.py
   gocept.lms/trunk/src/gocept/lms/notify.txt

Modified: gocept.lms/trunk/src/gocept/lms/app.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/app.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/app.py	Wed Jul 16 10:26:18 2008
(at)(at) -123,11 +123,11 (at)(at)
 
     (at)authenticatedClient
     def getClientNotifications(self, client, password):
-        return client.notify
+        return gocept.lms.interfaces.INotifications(client).enabled
 
     (at)authenticatedClient
     def setClientNotifications(self, client, password, status):
-        client.notify = bool(status)
+        gocept.lms.interfaces.INotifications(client).enabled = bool(status)
 
     (at)authenticatedClient
     def registerURLs(self, client, password, urls):

Modified: gocept.lms/trunk/src/gocept/lms/client.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/client.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/client.py	Wed Jul 16 10:26:18 2008
(at)(at) -2,9 +2,14 (at)(at)
 # See also LICENSE.txt
 
 import datetime
+import xmlrpclib
+
 import pytz
 import BTrees
 import grok
+from hurry.query import Ge
+from hurry.query.set import AnyOf
+import hurry.query.interfaces
 import zope.interface
 import zope.component
 import zope.event
(at)(at) -15,6 +20,10 (at)(at)
 import gocept.lms.interfaces
 
 
+# Provide hook for tests to set up dummies
+ServerProxy  = xmlrpclib.ServerProxy
+
+
 class ClientContainer(grok.Container):
 
     zope.interface.implements(gocept.lms.interfaces.IClientProvider)
(at)(at) -31,9 +40,6 (at)(at)
     password = None
     contact_name = None
     contact_email = None
-    notify = True
-    last_notification = datetime.datetime(1970, 1, 1, tzinfo=pytz.UTC)
-    failed_notifications = 0
     callback = None
 
     urls = gocept.reference.ReferenceCollection(ensure_integrity=True)
(at)(at) -53,3 +59,42 (at)(at)
 
     def iter_urls(self):
         return iter(self.urls)
+
+    def notify(self):
+        """Send notifications about recently changed URLs."""
+        notifications = gocept.lms.interfaces.INotifications(self)
+        url_query = zope.component.getUtility(hurry.query.interfaces.IQuery)
+        urls = url_query.searchResults(
+            Ge(('urls', 'last_state_change'), notifications.last) &
+            AnyOf(('urls', 'clients'), [self]))
+        if not urls:
+            # No URLs have changed since last time. Nothing to do.
+            return
+
+        # XXX Memory usage
+        notifications = [(url.url, url.state, url.reason) for url in urls]
+        self_xmlrpc = ServerProxy(self.callback)
+        self_xmlrpc.updateManyStates(self.id, self.password, notifications)
+
+
+class Notifications(grok.Annotation, grok.Model):
+
+    grok.provides(gocept.lms.interfaces.INotifications)
+    grok.context(gocept.lms.interfaces.INotificationRecipient)
+
+    enabled = True
+    last = datetime.datetime(1970, 1, 1, tzinfo=pytz.UTC)
+    failed = 0
+
+    def notify(self):
+        """Perform notifications on the recipient."""
+        try:
+            self.__parent__.notify()
+        except:
+            # XXX logging
+            self.failed += 1
+            if self.failed > 20:
+                self.enabled = False
+        else:
+            self.failed = 0
+            self.last = datetime.datetime.now(pytz.UTC)

Modified: gocept.lms/trunk/src/gocept/lms/interfaces.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/interfaces.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/interfaces.py	Wed Jul 16 10:26:18 2008
(at)(at) -80,7 +80,37 (at)(at)
         """Retrieve a Client object by Id."""
 
 
-class IClient(zope.interface.Interface):
+class INotifications(zope.interface.Interface):
+    """Manage notifications for a recipient."""
+
+    enabled = zope.schema.Bool(
+        title=u'Notify?',
+        default=True)
+
+    last = zope.schema.Datetime(
+        title=u'Last notified')
+
+    failed = zope.schema.Int(
+        title=u'Number of failed notifications',
+        default=0)
+
+    def notify():
+        """Perform notifications on the recipient."""
+
+
+class INotificationRecipient(zope.interface.Interface):
+    """A physical notification recipient such as a client.
+
+    Provides a specific 'over-the-wire' implementation of how to notify the
+    recipient.
+
+    """
+
+    def notify():
+        """Send notifications."""
+
+
+class IClient(INotificationRecipient):
 
     id = zope.schema.TextLine(
         title=u'Client Id',
(at)(at) -96,17 +126,6 (at)(at)
     contact_email = z3c.schema.email.RFC822MailAddress(
         title=u'Contact e-mail address')
 
-    notify = zope.schema.Bool(
-        title=u'Notify?',
-        default=True)
-
-    last_notification = zope.schema.Datetime(
-        title=u'Last notified')
-
-    failed_notifications = zope.schema.Int(
-        title=u'Number of failed notifications',
-        default=0)
-
     callback = zope.schema.URI(
         title=u'Callback URL')
 

Modified: gocept.lms/trunk/src/gocept/lms/notify.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/notify.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/notify.py	Wed Jul 16 10:26:18 2008
(at)(at) -2,55 +2,25 (at)(at)
 # See also LICENSE.txt
 
 import datetime
-
 import pytz
-
-from hurry.query import Ge
-from hurry.query.set import AnyOf
-import hurry.query.interfaces
 import zope.component
-
 import gocept.lms.interfaces
 
-import xmlrpclib
-
 
 INTERVAL = datetime.timedelta(seconds=5*60)
 
 
-# Provide hook for tests to set up dummies
-ServerProxy  = xmlrpclib.ServerProxy
-
-
 def notify():
-    url_query = zope.component.getUtility(hurry.query.interfaces.IQuery)
-
     reference_time = datetime.datetime.now(pytz.UTC) - INTERVAL
 
     # Notify each client about URL changes that happened since the last
     # notification.
     clients = zope.component.getUtility(gocept.lms.interfaces.IClientProvider)
     for client in clients.values():
-        if not client.notify:
+        notifications = gocept.lms.interfaces.INotifications(client)
+        if not notifications.enabled:
             continue
-        if client.last_notification > reference_time:
+        if notifications.last > reference_time:
             # don't notify clients more frequently than every 5 minutes
             continue
-        urls = url_query.searchResults(
-            Ge(('urls', 'last_state_change'), client.last_notification) &
-            AnyOf(('urls', 'clients'), [client]))
-        if not urls:
-            # No URLs have changed since last time. Just ignore this client.
-            continue
-        # XXX Memory usage
-        notifications = [(url.url, url.state, url.reason) for url in urls]
-        try:
-            client_xmlrpc = ServerProxy(client.callback)
-            client_xmlrpc.updateManyStates(client.id, client.password,
notifications)
-        except:
-            client.failed_notifications += 1
-            if client.failed_notifications > 20:
-                client.notify = False
-        else:
-            client.failed_notifications = 0
-            client.last_notification = datetime.datetime.now(pytz.UTC)
+        notifications.notify()

Modified: gocept.lms/trunk/src/gocept/lms/notify.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/notify.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/notify.txt	Wed Jul 16 10:26:18 2008
(at)(at) -21,7 +21,7 (at)(at)
 ...       print '\n'.join(self.log)
 ...       self.log = []
 >>> import gocept.lms.notify
->>> gocept.lms.notify.ServerProxy = xmlrpc = XMLRPCDummy()
+>>> gocept.lms.client.ServerProxy = xmlrpc = XMLRPCDummy()
 >>> import datetime
 >>> gocept.lms.notify.INTERVAL = datetime.timedelta(seconds=0)
 
(at)(at) -79,27 +79,28 (at)(at)
 There are two conditions for a client to not be notified. The first is if the
 client's notifications are disabled:
 
->>> fred.last_notification = fred.__class__.last_notification
->>> fred.notify = False
+>>> fred_notifications = gocept.lms.interfaces.INotifications(fred)
+>>> fred_notifications.last = fred_notifications.__class__.last
+>>> fred_notifications.enabled = False
 >>> notify()
 >>> xmlrpc.show_log()
 
 The client's last notification date has not been updated because he didn't
 receive a notification:
 
->>> fred.last_notification
+>>> fred_notifications.last
 datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>)
 
 Clean-up:
 
->>> fred.notify = True
+>>> fred_notifications.enabled = True
 
 The second is if the client should receive notifications but has received one
 a short time ago:
 
 >>> import pytz
 >>> now = datetime.datetime.now(pytz.UTC)
->>> fred.last_notification = now
+>>> fred_notifications.last = now
 >>> gocept.lms.notify.INTERVAL = datetime.timedelta(seconds=2)
 
 Ensure that a URL was changed recently enough for the client to be notified

SVN: r6087 - gocept.lms/trunk/src/gocept/lms
Thomas Lotze <tl(at)gocept.com>
2008-07-16 11:33:23 [ FULL ]
Author: thomas
Date: Wed Jul 16 11:33:20 2008
New Revision: 6087

Log:
started working on the checker: implemented the thread pool and the one-time
check runner


Added:
   gocept.lms/trunk/src/gocept/lms/check.py   (contents, props changed)
   gocept.lms/trunk/src/gocept/lms/check.txt   (contents, props changed)
Modified:
   gocept.lms/trunk/src/gocept/lms/interfaces.py
   gocept.lms/trunk/src/gocept/lms/tests.py

Added: gocept.lms/trunk/src/gocept/lms/check.py
==============================================================================
--- (empty file)
+++ gocept.lms/trunk/src/gocept/lms/check.py	Wed Jul 16 11:33:20 2008
(at)(at) -0,0 +1,67 (at)(at)
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+import datetime
+import threading
+
+import pytz
+
+import grok
+import zc.queue.interfaces
+import zope.component
+import zope.interface
+
+import gocept.lms.interfaces
+
+
+INTERVAL = datetime.timedelta(seconds=5*60)
+
+THREADS = 20
+
+
+def check():
+    """Pull URLs from the check queue and have them checked.
+
+    Does so as long as there are threads available.
+
+    """
+    check_queue = zope.component.getUtility(zc.queue.interfaces.IQueue,
+                                            name='check')
+    thread_pool = zope.component.getUtility(gocept.lms.interfaces.IThreadPool)
+
+    while True:
+        if not thread_pool.threads.acquire(blocking=False):
+            break
+        try:
+            url = check_queue.pull()
+        except:
+            thread_pool.threads.release()
+            break
+
+        thread = CheckerThread(url.url)
+        thread.start()
+        thread_pool.active.add(thread)
+
+
+class ThreadPool(grok.GlobalUtility):
+
+    zope.interface.implements(gocept.lms.interfaces.IThreadPool)
+
+    def __init__(self):
+        self.threads = threading.Semaphore(THREADS)
+        self.active = set()
+
+
+class CheckerThread(threading.Thread):
+
+    zope.interface.implements(gocept.lms.interfaces.ICheckerThread)
+
+    state = None
+    reason = None
+
+    def __init__(self, url):
+        super(CheckerThread, self).__init__()
+        self.url = url
+
+    def run(self):
+        pass

Added: gocept.lms/trunk/src/gocept/lms/check.txt
==============================================================================
--- (empty file)
+++ gocept.lms/trunk/src/gocept/lms/check.txt	Wed Jul 16 11:33:20 2008
(at)(at) -0,0 +1,114 (at)(at)
+===============
+The URL checker
+===============
+
+The checker pulls URLs from the check queue and checks them[#functionaltest]_.
+
+
+Pulling URLs from the check queue
+=================================
+
+A thread pool is used for managing worker threads. We reduce the maximum
+number of concurrent active threads for this test:
+
+>>> from zope.component import getUtility
+>>> import gocept.lms.interfaces
+>>> import threading
+>>> thread_pool = getUtility(gocept.lms.interfaces.IThreadPool)
+>>> thread_pool.threads = threading.Semaphore(3)
+>>> thread_pool.active
+set([])
+>>> thread_pool.threads._Semaphore__value
+3
+
+If the check queue is empty, running the checker will not create any worker
+threads:
+
+>>> from gocept.lms.check import check
+>>> check()
+>>> thread_pool.active
+set([])
+>>> thread_pool.threads._Semaphore__value
+3
+
+We create some URLs:
+
+>>> urls =
zope.component.getUtility(gocept.lms.interfaces.IURLProvider)
+>>> url1 = urls.add('http://example.com/1')
+>>> url2 = urls.add('http://example.com/2')
+>>> url3 = urls.add('http://example.com/3')
+>>> url4 = urls.add('http://example.com/4')
+>>> url5 = urls.add('http://example.com/5')
+
+Now put the first one in the check queue:
+
+>>> import zc.queue.interfaces
+>>> check_queue =
zope.component.getUtility(zc.queue.interfaces.IQueue,
+...                                         name='check')
+>>> check_queue.put(url1)
+
+The checker will empty the queue and create one worker thread:
+
+>>> check()
+>>> list(check_queue)
+[]
+>>> t1 = list(thread_pool.active)[0]
+>>> t1.join(2)
+>>> thread_pool.active
+set([<CheckerThread(Thread-1, stopped)>])
+>>> thread_pool.threads._Semaphore__value
+2
+
+Two more URLs can be checked until the thread limit is reached:
+
+>>> check_queue.put(url2)
+>>> check_queue.put(url3)
+>>> check()
+>>> list(check_queue)
+[]
+>>> _ = [t.join(2) for t in thread_pool.active]
+>>> sorted(thread_pool.active, key=lambda t:t.getName())
+[<CheckerThread(Thread-1, stopped)>,
+ <CheckerThread(Thread-2, stopped)>,
+ <CheckerThread(Thread-3, stopped)>]
+>>> thread_pool.threads._Semaphore__value
+0
+
+Now the checker cannot create any more worker threads. It will not take URLs
+from the queue:
+
+>>> check_queue.put(url4)
+>>> check()
+>>> list(check_queue)
+[<gocept.lms.url.URL 'http://example.com/4'>]
+>>> len(thread_pool.active)
+3
+>>> thread_pool.threads._Semaphore__value
+0
+
+After releasing one thread on the semaphore, we can continue to pull URLs from
+the queue. Remaining URLs that cannot be pulled because threads are used up
+again will stay in the queue:
+
+>>> thread_pool.threads.release()
+>>> check_queue.put(url5)
+>>> check()
+>>> list(check_queue)
+[<gocept.lms.url.URL 'http://example.com/5'>]
+>>> _ = [t.join(2) for t in thread_pool.active]
+>>> len(thread_pool.active)
+4
+>>> thread_pool.threads._Semaphore__value
+0
+
+
+.. [#functionaltest] Setup functional test
+
+    >>> import gocept.lms.app
+    >>> root = getRootFolder()
+    >>> import zope.app.component.hooks
+    >>> old_site = zope.app.component.hooks.getSite()
+    >>> zope.app.component.hooks.setSite(root)
+
+    >>> root['app'] = gocept.lms.app.LMS()
+    >>> zope.app.component.hooks.setSite(root['app'])

Modified: gocept.lms/trunk/src/gocept/lms/interfaces.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/interfaces.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/interfaces.py	Wed Jul 16 11:33:20 2008
(at)(at) -138,3 +138,27 (at)(at)
         urls: an iterable of IURL objects
 
         """
+
+
+class IThreadPool(zope.interface.Interface):
+    """Thread pool for the checker."""
+
+    threads = zope.interface.Attribute(
+        u'Semaphore limiting the number of concurrent active threads')
+
+    active = zope.interface.Attribute(u'Set of active threads')
+
+
+class ICheckerThread(zope.interface.Interface):
+
+    url = zope.schema.URI(
+        title=u'URL')
+
+    state = zope.schema.Choice(
+        title=u'State',
+        source=StateSource(),
+        required=False)
+
+    reason = zope.schema.TextLine(
+        title=u'Reason for state',
+        required=False)

Modified: gocept.lms/trunk/src/gocept/lms/tests.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/tests.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/tests.py	Wed Jul 16 11:33:20 2008
(at)(at) -24,7 +24,7 (at)(at)
         'url.txt',
         optionflags=flags)
     functional = zope.app.testing.functional.FunctionalDocFileSuite(
-        'app.txt', 'schedule.txt', 'xmlrpc.txt', 'notify.txt',
+        'app.txt', 'check.txt', 'schedule.txt', 'xmlrpc.txt', 'notify.txt',
         optionflags=flags)
     functional.layer = FunctionalLayer
     return unittest.TestSuite([unit, functional])

SVN: r6088 - gocept.lms/trunk/src/gocept/lms
Thomas Lotze <tl(at)gocept.com>
2008-07-16 11:55:08 [ FULL ]
Author: thomas
Date: Wed Jul 16 11:55:06 2008
New Revision: 6088

Log:
changed the thread pool implementation to get rid of the semaphore


Modified:
   gocept.lms/trunk/src/gocept/lms/check.py
   gocept.lms/trunk/src/gocept/lms/check.txt
   gocept.lms/trunk/src/gocept/lms/interfaces.py

Modified: gocept.lms/trunk/src/gocept/lms/check.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/check.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/check.py	Wed Jul 16 11:55:06 2008
(at)(at) -16,8 +16,6 (at)(at)
 
 INTERVAL = datetime.timedelta(seconds=5*60)
 
-THREADS = 20
-
 
 def check():
     """Pull URLs from the check queue and have them checked.
(at)(at) -30,14 +28,12 (at)(at)
     thread_pool = zope.component.getUtility(gocept.lms.interfaces.IThreadPool)
 
     while True:
-        if not thread_pool.threads.acquire(blocking=False):
+        if not thread_pool.available:
             break
-        try:
-            url = check_queue.pull()
-        except:
-            thread_pool.threads.release()
+        if not check_queue:
             break
 
+        url = check_queue.pull()
         thread = CheckerThread(url.url)
         thread.start()
         thread_pool.active.add(thread)
(at)(at) -47,10 +43,18 (at)(at)
 
     zope.interface.implements(gocept.lms.interfaces.IThreadPool)
 
+    limit = 20
+
     def __init__(self):
-        self.threads = threading.Semaphore(THREADS)
         self.active = set()
 
+    (at)property
+    def available(self):
+        remaining = self.limit - len(self.active)
+        if remaining < 0:
+            return 0
+        return remaining
+
 
 class CheckerThread(threading.Thread):
 

Modified: gocept.lms/trunk/src/gocept/lms/check.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/check.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/check.txt	Wed Jul 16 11:55:06 2008
(at)(at) -15,10 +15,10 (at)(at)
 >>> import gocept.lms.interfaces
 >>> import threading
 >>> thread_pool = getUtility(gocept.lms.interfaces.IThreadPool)
->>> thread_pool.threads = threading.Semaphore(3)
+>>> thread_pool.limit = 3
 >>> thread_pool.active
 set([])
->>> thread_pool.threads._Semaphore__value
+>>> thread_pool.available
 3
 
 If the check queue is empty, running the checker will not create any worker
(at)(at) -28,7 +28,7 (at)(at)
 >>> check()
 >>> thread_pool.active
 set([])
->>> thread_pool.threads._Semaphore__value
+>>> thread_pool.available
 3
 
 We create some URLs:
(at)(at) -56,7 +56,7 (at)(at)
 >>> t1.join(2)
 >>> thread_pool.active
 set([<CheckerThread(Thread-1, stopped)>])
->>> thread_pool.threads._Semaphore__value
+>>> thread_pool.available
 2
 
 Two more URLs can be checked until the thread limit is reached:
(at)(at) -71,7 +71,7 (at)(at)
 [<CheckerThread(Thread-1, stopped)>,
  <CheckerThread(Thread-2, stopped)>,
  <CheckerThread(Thread-3, stopped)>]
->>> thread_pool.threads._Semaphore__value
+>>> thread_pool.available
 0
 
 Now the checker cannot create any more worker threads. It will not take URLs
(at)(at) -83,22 +83,22 (at)(at)
 [<gocept.lms.url.URL 'http://example.com/4'>]
 >>> len(thread_pool.active)
 3
->>> thread_pool.threads._Semaphore__value
+>>> thread_pool.available
 0
 
-After releasing one thread on the semaphore, we can continue to pull URLs from
-the queue. Remaining URLs that cannot be pulled because threads are used up
-again will stay in the queue:
+After releasing one thread, we can continue to pull URLs from the queue.
+Remaining URLs that cannot be pulled because threads are used up again will
+stay in the queue:
 
->>> thread_pool.threads.release()
+>>> _ = thread_pool.active.pop()
 >>> check_queue.put(url5)
 >>> check()
 >>> list(check_queue)
 [<gocept.lms.url.URL 'http://example.com/5'>]
 >>> _ = [t.join(2) for t in thread_pool.active]
 >>> len(thread_pool.active)
-4
->>> thread_pool.threads._Semaphore__value
+3
+>>> thread_pool.available
 0
 
 

Modified: gocept.lms/trunk/src/gocept/lms/interfaces.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/interfaces.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/interfaces.py	Wed Jul 16 11:55:06 2008
(at)(at) -143,11 +143,15 (at)(at)
 class IThreadPool(zope.interface.Interface):
     """Thread pool for the checker."""
 
-    threads = zope.interface.Attribute(
-        u'Semaphore limiting the number of concurrent active threads')
+    limit = zope.schema.Int(
+        title=u'Maximum number of concurrent active threads')
 
     active = zope.interface.Attribute(u'Set of active threads')
 
+    available = zope.schema.Int(
+        title=u'Number of currently available threads',
+        readonly=True)
+
 
 class ICheckerThread(zope.interface.Interface):

SVN: r6098 - gocept.lms/trunk/profiles
Christian Theune <ct(at)gocept.com>
2008-07-16 19:34:35 [ FULL ]
Author: ctheune
Date: Wed Jul 16 19:34:34 2008
New Revision: 6098

Log:
Remove i18n for now. This makes trouble with the lovely recipe.



Modified:
   gocept.lms/trunk/profiles/base.cfg

Modified: gocept.lms/trunk/profiles/base.cfg
==============================================================================
--- gocept.lms/trunk/profiles/base.cfg	(original)
+++ gocept.lms/trunk/profiles/base.cfg	Wed Jul 16 19:34:34 2008
(at)(at) -1,6 +1,6 (at)(at)
 [buildout]
 develop = . gocept.reference
-parts = eggbasket app data zodb zeo lms i18n test runners scheduler checker
notifier
+parts = eggbasket app data zodb zeo lms test runners scheduler checker
notifier
 extends = versions.cfg
 # eggs will be installed in the default buildout location
 # (see .buildout/default.cfg in your home directory)
(at)(at) -77,14 +77,6 (at)(at)
 eggs = gocept.lms
 defaults = ['--tests-pattern', '^f?tests$', '-v', '-c']
 
-# this section named so that the i18n scripts are called bin/i18n...
-[i18n]
-recipe = lovely.recipe:i18n
-package = gocept.lms
-domain = gocept.lms
-location = src/gocept/lms
-output = locales
-
 [eggbasket]
 recipe = z3c.recipe.eggbasket
 eggs = grok

SVN: r6099 - gocept.lms/trunk/profiles
Christian Theune <ct(at)gocept.com>
2008-07-16 20:10:54 [ FULL ]
Author: ctheune
Date: Wed Jul 16 20:10:52 2008
New Revision: 6099

Log:
remove duplicate server definition



Modified:
   gocept.lms/trunk/profiles/base.cfg

Modified: gocept.lms/trunk/profiles/base.cfg
==============================================================================
--- gocept.lms/trunk/profiles/base.cfg	(original)
+++ gocept.lms/trunk/profiles/base.cfg	Wed Jul 16 20:10:52 2008
(at)(at) -67,10 +67,6 (at)(at)
             storage 1
         </zeoclient>
     </zodb>
-    <server>
-      address ${lms:address}
-      type HTTP
-    </server>
 
 [test]
 recipe = zc.recipe.testrunner

SVN: r6103 - gocept.lms/trunk/src/gocept/lms
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 10:11:35 [ FULL ]
Author: zagy
Date: Thu Jul 17 10:11:33 2008
New Revision: 6103

Log:
added unregisterURLs implementation


Modified:
   gocept.lms/trunk/src/gocept/lms/app.py
   gocept.lms/trunk/src/gocept/lms/app.txt
   gocept.lms/trunk/src/gocept/lms/client.py
   gocept.lms/trunk/src/gocept/lms/interfaces.py
   gocept.lms/trunk/src/gocept/lms/url.py
   gocept.lms/trunk/src/gocept/lms/xmlrpc.txt

Modified: gocept.lms/trunk/src/gocept/lms/app.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/app.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/app.py	Thu Jul 17 10:11:33 2008
(at)(at) -143,9 +143,13 (at)(at)
                   if url.state is not None]
         return result
 
-    def unregisterURLs(self, client_id, password, urls):
-        # XXX API stub
-        pass
+    (at)authenticatedClient
+    def unregisterURLs(self, client, password, urls):
+        url_objects = (url for url in 
+                       (self.url_provider.get_url(url) for url in urls)
+                       if url is not None)
+        client.unregister_urls(url_objects)
+        return True
 
     def getInfoFrameURL(self, client_id, password):
         # XXX API stub

Modified: gocept.lms/trunk/src/gocept/lms/app.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/app.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/app.txt	Thu Jul 17 10:11:33 2008
(at)(at) -41,7 +41,7 (at)(at)
 
 >>> fred.register_urls([urls.add('http://example.com/'),
 ...                     urls.add('http://example.com/asdf')])
->>> list(sorted(fred.iter_urls(), key=lambda x:x.url))
+>>> list(sorted(fred.iter_urls()))
 [<gocept.lms.url.URL 'http://example.com/'>,
  <gocept.lms.url.URL 'http://example.com/asdf'>]
 
(at)(at) -55,6 +55,24 (at)(at)
 >>> len(list(example.clients))
 1
 
+The URLs can be unregistered using the ``unreigster_urls`` method. When an URL
+is passed which is not registered for the client no error will be raised:
+
+>>> fred.unregister_urls(
+...     [urls.get_url('http://example.com/'),
+...      urls.add('http://not-registered-yet/')])
+>>> list(fred.iter_urls())
+[<gocept.lms.url.URL 'http://example.com/asdf'>]
+
+The URL http://example.com/ has no client
now:
+
+>>> list(example.clients)
+[]
+
+But /asdf still has
+>>> list(urls.get_url('http://example.com/asdf').clients)
+[<gocept.lms.client.Client object at 0x...>]
+
 
 Clean up
 --------

Modified: gocept.lms/trunk/src/gocept/lms/client.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/client.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/client.py	Thu Jul 17 10:11:33 2008
(at)(at) -52,12 +52,15 (at)(at)
 
     def register_urls(self, urls):
         self.urls.update(urls)
-        references = zope.component.getUtility(
-            zc.relation.interfaces.ICatalog, name='url_refs')
-        references.index(self)
+        self._reindex(urls)
+
+    def unregister_urls(self, urls):
         for url in urls:
-            # Trigger re-indexing
-            zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url))
+            try:
+                self.urls.remove(url)
+            except KeyError:
+                pass
+        self._reindex(urls)
 
     def iter_urls(self):
         return iter(self.urls)
(at)(at) -78,6 +81,15 (at)(at)
         self_xmlrpc = ServerProxy(self.callback)
         self_xmlrpc.updateManyStates(self.id, self.password, notifications)
 
+    def _reindex(self, urls):
+        """Helper to reindex when urls change."""
+        references = zope.component.getUtility(
+            zc.relation.interfaces.ICatalog, name='url_refs')
+        references.index(self)
+        for url in urls:
+            # Trigger re-indexing
+            zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(url))
+
 
 class Notifications(grok.Annotation, grok.Model):
 

Modified: gocept.lms/trunk/src/gocept/lms/interfaces.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/interfaces.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/interfaces.py	Thu Jul 17 10:11:33 2008
(at)(at) -139,6 +139,13 (at)(at)
 
         """
 
+    def unregister_urls(urls):
+        """Unregister URLs for client.
+
+        urls: an iterable of IURL objects
+
+        """
+
 
 class IThreadPool(zope.interface.Interface):
     """Thread pool for the checker."""

Modified: gocept.lms/trunk/src/gocept/lms/url.py
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/url.py	(original)
+++ gocept.lms/trunk/src/gocept/lms/url.py	Thu Jul 17 10:11:33 2008
(at)(at) -49,6 +49,11 (at)(at)
         return "<%s.%s '%s'>" % (
             __name__, self.__class__.__name__, self.url)
 
+    def __cmp__(self, other):
+        if isinstance(other, URL):
+            return cmp(self.url, other.url)
+        return -1
+
     (at)property
     def clients(self):
         references = zope.component.getUtility(

Modified: gocept.lms/trunk/src/gocept/lms/xmlrpc.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/xmlrpc.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/xmlrpc.txt	Thu Jul 17 10:11:33 2008
(at)(at) -79,8 +79,8 (at)(at)
 >>> clients =
zope.component.getUtility(gocept.lms.interfaces.IClientProvider)
 >>> list(clients)
 [u'gocept']
->>> gocept = clients.get('gocept')
->>> list(gocept.iter_urls())
+>>> cl_gocept = clients.get('gocept')
+>>> list(cl_gocept.iter_urls())
 [<gocept.lms.url.URL 'http://example.com/url1'>,
  <gocept.lms.url.URL 'http://example.com/url2'>]
 
(at)(at) -122,6 +122,30 (at)(at)
 
 XXX what happens when invalid urls are registered?
 
+
+
+unregisterURLs
+==============
+
+The client calls ``unregisterURLs`` if he doesn't want to get further
+notifications for URLs[#unregisterURLs-auth]_:
+
+>>> server.unregisterURLs(
+...     'gocept', password,
+...     ['http://example.com/url2',
+...      'http://example.com/url3'])
+True
+
+After calling unregisterURLs those URLs are no longer int he client's
+list[#functionaltest]_:
+
+>>> transaction.abort()
+>>> list(cl_gocept.iter_urls())
+[<gocept.lms.url.URL 'http://example.com/url1'>]
+
+>>> zope.app.component.hooks.setSite(old_site)
+
+
 Setting/getting the notification status
 =======================================
 
(at)(at) -149,7 +173,6 (at)(at)
 Various API methods, currently implemented as stubs
 ===================================================
 
->>> server.unregisterURLs('gocept', password, ['http://localhost'])
 >>> server.getInfoFrameURL('gocept', password)
 'http://localhost:8080/lms'
 
(at)(at) -161,6 +184,13 (at)(at)
         ...
     ProtocolError: <ProtocolError for localhost/app/: 401 401
Unauthorized>
 
+.. [#unregisterURLs-auth] unregisterURLs needs authentication:
+
+    >>> server.unregisterURLs('gocept', 'bsdf', [])
+    Traceback (most recent call last):
+        ...
+    ProtocolError: <ProtocolError for localhost/app/: 401 401
Unauthorized>
+
 .. [#functionaltest] Setup functional test
 
     >>> zope.app.component.hooks.setSite(root['app'])

SVN: r6111 - gocept.lms/trunk
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 14:45:44 [ FULL ]
Author: zagy
Date: Thu Jul 17 14:45:43 2008
New Revision: 6111

Log:
sorted dependencies


Modified:
   gocept.lms/trunk/setup.py

Modified: gocept.lms/trunk/setup.py
==============================================================================
--- gocept.lms/trunk/setup.py	(original)
+++ gocept.lms/trunk/setup.py	Thu Jul 17 14:45:43 2008
(at)(at) -18,19 +18,19 (at)(at)
       zip_safe=False,
       install_requires=[
           'decorator',
+          'gocept.reference>=0.4dev',
           'grok',
           'hurry.query',
           'setuptools',
           'z3c.schema',
           'z3c.testsetup',
           'zc.catalog',
-          'zc.relation',
           'zc.queue',
+          'zc.relation',
           'zc.sourcefactory',
-          'zope.testing',
           'zope.app.twisted',
           'zope.app.wsgi',
-          'gocept.reference>=0.4dev'
+          'zope.testing',
       ],
       entry_points = dict(
         console_scripts =

SVN: r6116 - gocept.form/tags/0.7.5
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 16:15:30 [ FULL ]
Author: zagy
Date: Thu Jul 17 16:15:29 2008
New Revision: 6116

Log:
tagging 0.7.5



Added:
   gocept.form/tags/0.7.5/
      - copied from r6115, gocept.form/trunk/

SVN: r6117 - gocept.form/tags/0.7.5
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 16:15:51 [ FULL ]
Author: zagy
Date: Thu Jul 17 16:15:50 2008
New Revision: 6117

Log:
0.7.5


Modified:
   gocept.form/tags/0.7.5/setup.py

Modified: gocept.form/tags/0.7.5/setup.py
==============================================================================
--- gocept.form/tags/0.7.5/setup.py	(original)
+++ gocept.form/tags/0.7.5/setup.py	Thu Jul 17 16:15:50 2008
(at)(at) -9,7 +9,7 (at)(at)
 
 setup(
     name = 'gocept.form',
-    version = "0.7.5dev",
+    version = "0.7.5",
     author = "Christian Zagrodnick",
     author_email = "cz(at)gocept.com",
     description = "Extensions for zope.formlib",

SVN: r6118 - gocept.form/trunk
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 16:16:45 [ FULL ]
Author: zagy
Date: Thu Jul 17 16:16:44 2008
New Revision: 6118

Log:
vb


Modified:
   gocept.form/trunk/README.txt
   gocept.form/trunk/setup.py

Modified: gocept.form/trunk/README.txt
==============================================================================
--- gocept.form/trunk/README.txt	(original)
+++ gocept.form/trunk/README.txt	Thu Jul 17 16:16:44 2008
(at)(at) -31,6 +31,9 (at)(at)
 Changes
 =======
 
+0.7.6 (unreleased)
+------------------
+
 0.7.5 (2008-07-17)
 ------------------
 

Modified: gocept.form/trunk/setup.py
==============================================================================
--- gocept.form/trunk/setup.py	(original)
+++ gocept.form/trunk/setup.py	Thu Jul 17 16:16:44 2008
(at)(at) -9,7 +9,7 (at)(at)
 
 setup(
     name = 'gocept.form',
-    version = "0.7.5dev",
+    version = "0.7.6dev",
     author = "Christian Zagrodnick",
     author_email = "cz(at)gocept.com",
     description = "Extensions for zope.formlib",

SVN: r6127 - gocept.form/tags/0.7.6
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 17:37:14 [ FULL ]
Author: zagy
Date: Thu Jul 17 17:37:13 2008
New Revision: 6127

Log:
tagging 0.7.6



Added:
   gocept.form/tags/0.7.6/
      - copied from r6126, gocept.form/trunk/

SVN: r6128 - gocept.form/tags/0.7.6
Christian Zagrodnick <cz(at)gocept.com>
2008-07-17 17:37:37 [ FULL ]
Author: zagy
Date: Thu Jul 17 17:37:36 2008
New Revision: 6128

Log:
0.7.6


Modified:
   gocept.form/tags/0.7.6/setup.py

Modified: gocept.form/tags/0.7.6/setup.py
==============================================================================
--- gocept.form/tags/0.7.6/setup.py	(original)
+++ gocept.form/tags/0.7.6/setup.py	Thu Jul 17 17:37:36 2008
(at)(at) -9,7 +9,7 (at)(at)
 
 setup(
     name = 'gocept.form',
-    version = "0.7.6dev",
+    version = "0.7.6",
     author = "Christian Zagrodnick",
     author_email = "cz(at)gocept.com",
     description = "Extensions for zope.formlib",

SVN: r6138 - gocept.lms/trunk/src/gocept/lms
Christian Zagrodnick <cz(at)gocept.com>
2008-07-18 13:44:57 [ FULL ]
Author: zagy
Date: Fri Jul 18 13:44:56 2008
New Revision: 6138

Log:
different msg on darwin


Modified:
   gocept.lms/trunk/src/gocept/lms/ftp.txt

Modified: gocept.lms/trunk/src/gocept/lms/ftp.txt
==============================================================================
--- gocept.lms/trunk/src/gocept/lms/ftp.txt	(original)
+++ gocept.lms/trunk/src/gocept/lms/ftp.txt	Fri Jul 18 13:44:56 2008
(at)(at) -32,4 +32,4 (at)(at)
 If the server doesn't respond, we mark it as `unavailable` as well:
 
 >>> handler.check('ftp://no.server.example/knuddlhurz')
-('unavailable', 'Name or service not known')
+('unavailable', '...not known')

SVN: r6150 - gocept.lms/trunk
Christian Theune <ct(at)gocept.com>
2008-07-21 14:49:11 [ FULL ]
Author: ctheune
Date: Mon Jul 21 14:49:10 2008
New Revision: 6150

Log:
Remove external for gocept.munin again, only the plugin script needs this and
that runs with the system python.



Modified:
   gocept.lms/trunk/   (props changed)

SVN: r6153 - gocept.sequence/tags
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-21 15:15:17 [ FULL ]
Author: sweh
Date: Mon Jul 21 15:15:15 2008
New Revision: 6153

Log:
create tags directory



Added:
   gocept.sequence/tags/

SVN: r6154 - gocept.sequence/tags/0.3dev
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-21 15:16:22 [ FULL ]
Author: sweh
Date: Mon Jul 21 15:16:21 2008
New Revision: 6154

Log:
copy trunk to new 0.3dev tag



Added:
   gocept.sequence/tags/0.3dev/
      - copied from r6153, gocept.sequence/trunk/

SVN: r6155 - in gocept.sequence/tags: 0.3 0.3dev
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-21 15:18:06 [ FULL ]
Author: sweh
Date: Mon Jul 21 15:18:06 2008
New Revision: 6155

Log:
remove the dev in tag



Added:
   gocept.sequence/tags/0.3/
      - copied from r6154, gocept.sequence/tags/0.3dev/
Removed:
   gocept.sequence/tags/0.3dev/

SVN: r6156 - gocept.sequence/tags/0.3
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-21 15:19:17 [ FULL ]
Author: sweh
Date: Mon Jul 21 15:19:16 2008
New Revision: 6156

Log:
change version



Modified:
   gocept.sequence/tags/0.3/setup.py

Modified: gocept.sequence/tags/0.3/setup.py
==============================================================================
--- gocept.sequence/tags/0.3/setup.py	(original)
+++ gocept.sequence/tags/0.3/setup.py	Mon Jul 21 15:19:16 2008
(at)(at) -4,7 +4,7 (at)(at)
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
-version = '0.2dev'
+version = '0.3'
 
 setup(name='gocept.sequence',
       version=version,

SVN: r6167 - gocept.sequence/tags/0.31
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-22 10:05:56 [ FULL ]
Author: sweh
Date: Tue Jul 22 10:05:55 2008
New Revision: 6167

Log:
create release tag



Added:
   gocept.sequence/tags/0.31/
      - copied from r6166, gocept.sequence/trunk/

SVN: r6168 - gocept.sequence/tags/0.31
Sebastian Wehrmann <sw(at)gocept.com>
2008-07-22 10:06:40 [ FULL ]
Author: sweh
Date: Tue Jul 22 10:06:39 2008
New Revision: 6168

Log:
change version



Modified:
   gocept.sequence/tags/0.31/setup.py

Modified: gocept.sequence/tags/0.31/setup.py
==============================================================================
--- gocept.sequence/tags/0.31/setup.py	(original)
+++ gocept.sequence/tags/0.31/setup.py	Tue Jul 22 10:06:39 2008
(at)(at) -4,7 +4,7 (at)(at)
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
-version = '0.4dev'
+version = '0.31'
 
 setup(name='gocept.sequence',
       version=version,

SVN: r6319 - gocept.imapapi
Christian Theune <ct(at)gocept.com>
2008-07-22 10:43:34 [ FULL ]
Author: ctheune
Date: Tue Jul 22 10:43:33 2008
New Revision: 6319

Log:
Create separate project are for the IMAPAPI.



Added:
   gocept.imapapi/

SVN: r6325 - gocept.sequence/trunk
Thomas Lotze <tl(at)gocept.com>
2008-07-22 11:16:08 [ FULL ]
Author: thomas
Date: Tue Jul 22 11:16:06 2008
New Revision: 6325

Log:
rewrote the short package description


Modified:
   gocept.sequence/trunk/README.txt

Modified: gocept.sequence/trunk/README.txt
==============================================================================
--- gocept.sequence/trunk/README.txt	(original)
+++ gocept.sequence/trunk/README.txt	Tue Jul 22 11:16:06 2008
(at)(at) -1,4 +1,6 (at)(at)
-This package lets you generate a persistent sequence. That means in practice,
-that you can generate sequent numbers without having to worry about storing
-the last generated number. This is done by gocept.sequence within the
-annotation of an object you provide.
+===============
+gocept.sequence
+===============
+
+This package generates increasing sequences of integer numbers associated with
+objects. It uses persistent annotations of those objects.

SVN: r6326 - gocept.recipe.env
Christian Theune <ct(at)gocept.com>
2008-07-22 12:03:03 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:03:02 2008
New Revision: 6326

Log:
Create project area for gocept.recipe.env



Added:
   gocept.recipe.env/

SVN: r6328 - gocept.recipe.env/trunk
Christian Theune <ct(at)gocept.com>
2008-07-22 12:06:20 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:06:20 2008
New Revision: 6328

Log:
Remove garbage from import.



Removed:
   gocept.recipe.env/trunk/.installed.cfg
   gocept.recipe.env/trunk/svn-commit.tmp

SVN: r6329 - gocept.recipe.env/trunk
Christian Theune <ct(at)gocept.com>
2008-07-22 12:06:54 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:06:53 2008
New Revision: 6329

Log:
start setting ignores



Modified:
   gocept.recipe.env/trunk/   (props changed)

SVN: r6331 - gocept.recipe.env/tags
Christian Theune <ct(at)gocept.com>
2008-07-22 12:51:54 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:51:53 2008
New Revision: 6331

Log:
Create tags area



Added:
   gocept.recipe.env/tags/

SVN: r6332 - gocept.recipe.env/1.0
Christian Theune <ct(at)gocept.com>
2008-07-22 12:52:10 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:52:09 2008
New Revision: 6332

Log:
Tag release



Added:
   gocept.recipe.env/1.0/
      - copied from r6331, gocept.recipe.env/trunk/

SVN: r6333 - gocept.recipe.env/trunk
Christian Theune <ct(at)gocept.com>
2008-07-22 12:53:54 [ FULL ]
Author: ctheune
Date: Tue Jul 22 12:53:53 2008
New Revision: 6333

Log:
version bump and some textual cleanup



Modified:
   gocept.recipe.env/trunk/CHANGES.txt
   gocept.recipe.env/trunk/CONTRIBUTORS.txt
   gocept.recipe.env/trunk/README.txt
   gocept.recipe.env/trunk/setup.py

Modified: gocept.recipe.env/trunk/CHANGES.txt
==============================================================================
--- gocept.recipe.env/trunk/CHANGES.txt	(original)
+++ gocept.recipe.env/trunk/CHANGES.txt	Tue Jul 22 12:53:53 2008
(at)(at) -1,4 +1,4 (at)(at)
 1.0 (2008-07-22)
 ================
 
- - Created recipe with ZopeSkel [Christian Theune <ct(at)gocept.com>].
+- Created recipe with ZopeSkel [Christian Theune <ct(at)gocept.com>].

Modified: gocept.recipe.env/trunk/CONTRIBUTORS.txt
==============================================================================
--- gocept.recipe.env/trunk/CONTRIBUTORS.txt	(original)
+++ gocept.recipe.env/trunk/CONTRIBUTORS.txt	Tue Jul 22 12:53:53 2008
(at)(at) -1,3 +1,3 (at)(at)
-Christian Theune <ct(at)gocept.com>, Author
-Thomas Lotze <tl(at)gocept.com>, Author
+* Christian Theune <ct(at)gocept.com>, Author
+* Thomas Lotze <tl(at)gocept.com>, Author
 

Modified: gocept.recipe.env/trunk/README.txt
==============================================================================
--- gocept.recipe.env/trunk/README.txt	(original)
+++ gocept.recipe.env/trunk/README.txt	Tue Jul 22 12:53:53 2008
(at)(at) -1,17 +1,4 (at)(at)
 .. contents::
 
-.. Note to recipe author!
-   ---------------------
-   Update the following URLs to point to your:
-   
-   - code repository
-   - bug tracker 
-   - questions/comments feedback mail 
-   (do not set a real mail, to avoid spams)
-
-   Or remove it if not used.
-[...]

SVN: r6340 - gocept.imapapi/tags
Christian Theune <ct(at)gocept.com>
2008-07-22 16:56:42 [ FULL ]
Author: ctheune
Date: Tue Jul 22 16:56:42 2008
New Revision: 6340

Log:
Create area for releases



Added:
   gocept.imapapi/tags/

SVN: r6341 - gocept.imapapi/tags/0.1
Christian Theune <ct(at)gocept.com>
2008-07-22 16:57:33 [ FULL ]
Author: ctheune
Date: Tue Jul 22 16:57:32 2008
New Revision: 6341

Log:
Mark 0.1, the initial public release.



Added:
   gocept.imapapi/tags/0.1/
      - copied from r6340, gocept.imapapi/trunk/

SVN: r6344 - gocept.lxml/trunk
Christian Zagrodnick <cz(at)gocept.com>
2008-07-24 21:46:40 [ FULL ]
Author: zagy
Date: Thu Jul 24 21:46:39 2008
New Revision: 6344

Log:
updated libxml2 and libxslt



Modified:
   gocept.lxml/trunk/buildout-lxml.cfg

Modified: gocept.lxml/trunk/buildout-lxml.cfg
==============================================================================
--- gocept.lxml/trunk/buildout-lxml.cfg	(original)
+++ gocept.lxml/trunk/buildout-lxml.cfg	Thu Jul 24 21:46:39 2008
(at)(at) -4,12 +4,12 (at)(at)
 
 [libxml2]
 recipe = zc.recipe.cmmi
-url = http://ftp.gnome.org/pub/GNOME/sources/libxml2/2.6/libxml2-2.6.30.tar.gz
+url = http://uter.gocept.com/mirror/libxml2-sources-2.6.32.tar.gz
 extra_options = --without-python
 
 [libxslt]
 recipe = zc.recipe.cmmi
-url = http://ftp.gnome.org/pub/GNOME/sources/libxslt/1.1/libxslt-1.1.22.tar.gz
+url = http://uter.gocept.com/mirror/libxslt-1.1.24.tar.gz
 extra_options = --with-libxml-prefix=${buildout:directory}/parts/libxml2/ 
                 --without-python

MailBoxer