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 30static BOOL debugOn = NO; 31static NSTimeInterval defaultLifeTime = 3600; 32static int expirationInterval = 10 * 60 /* every 10 minutes */; 33 34static 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 */ 280