]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WebDAV/SoSubscriptionManager.m
changed #includes into #imports - does compile against MulleEOF out of the box now
[sope] / sope-appserver / NGObjWeb / WebDAV / SoSubscriptionManager.m
1 /*
2   Copyright (C) 2002-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "SoSubscriptionManager.h"
23 #include "SoSubscription.h"
24 #include "SoObject.h"
25 #import <EOControl/EOControl.h>
26 #include "common.h"
27
28 @implementation SoSubscriptionManager
29
30 static BOOL           debugOn         = NO;
31 static NSTimeInterval defaultLifeTime = 3600;
32 static int            expirationInterval = 10 * 60 /* every 10 minutes */;
33
34 static SoSubscriptionManager *sm = nil;
35
36 + (id)sharedSubscriptionManager {
37   if (sm == nil) {
38     debugOn = [[NSUserDefaults standardUserDefaults] 
39                 boolForKey:@"SoSubscriptionManagerDebugEnabled"];
40     
41     sm = [[SoSubscriptionManager alloc] init];
42   }
43   return sm;
44 }
45
46 - (id)init {
47   if ((self = [super init])) {
48     self->idToSubscription = [[NSMutableDictionary alloc] init];
49
50     [[NSNotificationCenter defaultCenter] 
51       addObserver:self selector:@selector(trackChange:)
52       name:@"SoObjectChanged" object:nil];
53   }
54   return self;
55 }
56 - (void)dealloc {
57   [[NSNotificationCenter defaultCenter] removeObserver:self];
58   [self->idToSubscription release];
59   [super dealloc];
60 }
61
62 /* subscriptions */
63
64 - (NSString *)nextSubscriptionID {
65   static int i = 1000;
66   i++;
67   return [NSString stringWithFormat:@"%i", i];
68 }
69
70 - (NSString *)subscribeURL:(NSURL *)_url forObserver:(NSURL *)_callback
71   type:(NSString *)_type delay:(NSTimeInterval)_delay
72   lifetime:(NSTimeInterval)_lifetime
73 {
74   /* subscribe httpu://127.0.0.1:14970/ for 3600s (type update, delay 30s) */
75   SoSubscription *s;
76   NSString *sid;
77   
78   if (_lifetime == 0.0) _lifetime = defaultLifeTime;
79   sid = [self nextSubscriptionID];
80   
81   if (debugOn) {
82     [self debugWithFormat:@"subscribe url %@", _url];
83     [self debugWithFormat:@"  observer:   %@", _callback];
84     [self debugWithFormat:@"  type:       %@", _type];
85     [self debugWithFormat:@"  delay:      %i", (int)_delay];
86     [self debugWithFormat:@"SID:          %@", sid];
87   }
88   
89   s = [[SoSubscription alloc] initWithID:sid 
90                               url:_url observer:_callback type:_type
91                               delay:_delay lifetime:_lifetime];
92   [self->idToSubscription setObject:s forKey:sid];
93   [s autorelease];
94   
95   [self debugWithFormat:@"expires: %@", [s expirationDate]];
96
97   if (s != nil && self->timer == nil) {
98     self->timer = [[NSTimer scheduledTimerWithTimeInterval:expirationInterval
99                             target:self 
100                             selector:@selector(performExpirationCheck:)
101                             userInfo:nil
102                             repeats:YES]
103                             retain];
104   }
105   
106   return [s subscriptionID];
107 }
108
109 - (NSString *)renewSubscription:(NSString *)_sid onURL:(NSURL *)_url {
110   SoSubscription *s;
111   
112   if (debugOn)
113     [self debugWithFormat:@"renew subscription %@, url: %@", _sid, _url];
114   
115   if ((s = [self->idToSubscription objectForKey:_sid]) == nil) {
116     [self logWithFormat:
117             @"attempt to renew non-existing subscription '%@'", _sid];
118     return nil;
119   }
120   
121   if (![s isValidForURL:_url]) {
122     [self logWithFormat:
123             @"mismatch between subscription-id and subscribed resource"];
124     return nil;
125   }
126   
127   if (![s renewSubscription])
128     return _sid;
129   
130   return _sid;
131 }
132
133 - (BOOL)unsubscribeID:(NSString *)_sid onURL:(NSURL *)_url {
134   SoSubscription *s;
135   
136   if (_sid == nil) return NO;
137   if (_url == nil) return NO;
138
139   if ((s = [self->idToSubscription objectForKey:_sid]) == nil) {
140     [self debugWithFormat:@"no subscription with id '%@'", _sid];
141     return NO;
142   }
143   if (![s isValidForURL:_url]) {
144     [self logWithFormat:
145             @"mismatch between subscription-id and subscribed resource"];
146     return NO;
147   }
148   
149   [self->idToSubscription removeObjectForKey:_sid];
150   [self debugWithFormat:@"canceled subscription '%@'", _sid];
151   return YES;
152 }
153
154 - (id)pollSubscriptions:(NSArray *)_sids onURL:(NSURL *)_url {
155   NSEnumerator   *e;
156   NSMutableArray *pending  = nil;
157   NSMutableArray *inactive = nil;
158   NSString       *sid;
159   
160   e = [_sids objectEnumerator];
161   while ((sid = [e nextObject])) {
162     SoSubscription *s;
163     
164     if ((s = [self->idToSubscription objectForKey:sid]) == nil) {
165       [self debugWithFormat:@"no subscription with id '%@'", sid];
166       continue;
167     }
168     if (![s isValidForURL:_url]) {
169       [self debugWithFormat:@"subscription '%@' is not valid for given URL", 
170               sid];
171       continue;
172     }
173     
174     [s renewSubscription]; /* renew subscription on access ... */
175     
176     if ([s hasEventsPending]) {
177       [self debugWithFormat:@"events pending on sid '%@' ...", sid];
178       [s resetEvents];
179       if (pending == nil) pending = [[NSMutableArray alloc] init];
180       [pending addObject:sid];
181     }
182     else {
183       [self debugWithFormat:@"no events pending on sid '%@' ...", sid];
184       if (inactive == nil) inactive = [[NSMutableArray alloc] init];
185       [inactive addObject:sid];
186     }
187   }
188   
189   if (pending  == nil) pending  = [NSArray array];
190   if (inactive == nil) inactive = [NSArray array];
191   return [NSDictionary dictionaryWithObjectsAndKeys:
192                          pending,  @"pending",
193                          inactive, @"inactive",
194                          nil];
195 }
196
197 /* tracking changes */
198
199 - (void)trackChangedURL:(NSURL *)_url {
200   [self debugWithFormat:@"url was changed: %@", _url];
201 }
202 - (void)trackChangedPath:(NSString *)_path {
203   [self debugWithFormat:@"path was changed: %@", _path];
204 }
205 - (void)trackChangedObject:(id)_object {
206   id url = nil;
207
208   if ((url = [_object baseURLInContext:nil]))
209     url = [NSURL URLWithString:url];
210   
211   if (url) {
212     [self debugWithFormat:@"object with URL was changed: %@", _object];
213     [self trackChangedURL:url];
214   }
215   else
216     [self debugWithFormat:@"object without URL was changed: %@", _object];
217 }
218
219 - (void)trackChange:(NSNotification *)_notification {
220   id object;
221   
222   if ((object = [_notification object]) == nil) {
223     [self logWithFormat:@"missing changed object in change notification ..."];
224     return;
225   }
226   
227   if ([object isKindOfClass:[NSURL class]])
228     [self trackChangedURL:object];
229   else if ([object isKindOfClass:[NSString class]])
230     [self trackChangedPath:object];
231   else
232     [self trackChangedObject:object];
233 }
234
235 /* expiration check */
236
237 - (void)performExpirationCheck:(NSTimer *)_timer {
238   NSAutoreleasePool *pool;
239   NSArray        *subs;
240   NSEnumerator   *e;
241   SoSubscription *s;
242   
243   pool = [[NSAutoreleasePool alloc] init];
244
245   /* scan for expired subscriptions */
246   
247   subs = [[self->idToSubscription allValues] shallowCopy];
248   if (debugOn) {
249     [self debugWithFormat:@"perform expiration check (%i subscriptions) ...",
250             [subs count]];
251   }
252   
253   e = [subs objectEnumerator];
254   while ((s = [e nextObject])) {
255     if ([s isExpired]) {
256       if (debugOn)
257         [self debugWithFormat:@"  expired: %@", [s subscriptionID]];
258       [self->idToSubscription removeObjectForKey:[s subscriptionID]];
259     }
260   }
261   
262   /* remove timer if we have no subscriptions ... */
263   
264   if ([self->idToSubscription count] == 0) {
265     [self debugWithFormat:@"  no subscriptions left, removing timer .."];
266     [self->timer invalidate];
267     [self->timer release]; self->timer = nil;
268   }
269   
270   [pool release];
271 }
272
273 /* debugging */
274
275 - (BOOL)isDebuggingEnabled {
276   return debugOn;
277 }
278
279 @end /* SoSubscriptionManager */