]> err.no Git - sope/blob - sope-appserver/SoOFS/OFSPropertyListObject.m
synced with latest changes, bumped dyld versions
[sope] / sope-appserver / SoOFS / OFSPropertyListObject.m
1 /*
2   Copyright (C) 2000-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 "OFSPropertyListObject.h"
23 #include "OFSFactoryContext.h"
24 #include <WebDAV/SoObject+SoDAV.h>
25 #include "common.h"
26
27 @interface OFSPropertyListObjectClassDescription : NSClassDescription
28 {
29 @public
30   OFSPropertyListObject *object;
31 }
32
33 @end
34
35 @implementation OFSPropertyListObject
36
37 static int debugOn = 0;
38
39 + (int)version {
40   return [super version] + 0 /* v1 */;
41 }
42 + (void)initialize {
43   static BOOL didInit = NO;
44   if (!didInit) {
45     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
46     didInit = YES;
47     NSAssert2([super version] == 1,
48               @"invalid superclass (%@) version %i !",
49               NSStringFromClass([self superclass]), [super version]);
50     
51     debugOn = [ud boolForKey:@"SoOFSDebugPlistObject"] ? 1 : 0;
52   }
53 }
54
55 - (void)dealloc {
56   [self->recordKeys release];
57   [self->record     release];
58   [super dealloc];
59 }
60
61 /* storage */
62
63 - (NSStringEncoding)stringEncoding {
64   return [NSString defaultCStringEncoding];
65 }
66
67 - (void)setSoResourceClassName:(NSString *)_name {} // ignore
68 - (void)setSoClassName:(NSString *)_name         {} // ignore
69 - (NSString *)soResourceClassName { // deprecated
70   return [[self soClass] className];
71 }
72 - (NSString *)soClassName {
73   return [[self soClass] className];
74 }
75
76 - (NSArray *)attributeKeys {
77   [[self restoreObject] raise];
78   return self->recordKeys;
79 }
80
81 - (NSArray *)allKeys {
82   return [self attributeKeys];
83 }
84
85 - (NSClassDescription *)soClassDescription {
86   OFSPropertyListObjectClassDescription *cd;
87   cd = [[OFSPropertyListObjectClassDescription alloc] init];
88   cd->object = [self retain];
89   return [cd autorelease];
90 }
91
92 - (NSString *)contentAsString {
93   /* do not allow access to raw contents ! */
94   return nil;
95 }
96
97 - (void)removeSpecialKeysFromRestoreDictionary:(NSMutableDictionary *)_md {
98   /* remove special storage keys like SoClassName to plist */
99   [_md removeObjectForKey:@"SoClassName"];
100   [_md removeObjectForKey:@"SoResourceClassName"];
101 }
102 - (void)addSpecialKeysToSaveDictionary:(NSMutableDictionary *)_md {
103   /* add special storage keys like SoClassName to plist */
104   [_md setObject:[self soClassName] forKey:@"SoClassName"];
105 }
106
107 - (NSException *)restoreObject {
108   NSMutableDictionary *plist = nil;
109   NSException *e;
110   NSData      *content;
111   NSString    *s;
112   id fm;
113   
114   if (self->flags.isLoaded) return nil;
115   if ((fm = [self fileManager]) == nil) {
116     e = [NSException exceptionWithHTTPStatus:500
117                      reason:@"plist object has no filemanager ??"];
118     return e;
119   }
120   s = [self storagePath];
121   if ([s length] == 0) {
122     e = [NSException exceptionWithHTTPStatus:500
123                      reason:@"plist object has no storage path ??"];
124     return e;
125   }
126   
127   self->flags.isLoading = 1;
128   self->flags.isLoaded  = 1;
129   e = nil;
130   
131   /* load file, convert into string, then into a property list */
132   
133   if ((content = [fm contentsAtPath:s])==nil) {
134     if (([fm respondsToSelector:@selector(lastException)])) {
135       e = [fm lastException];
136       goto done;
137     }
138     else {
139       e = [NSException exceptionWithHTTPStatus:404 /* not found */
140                        reason:@"failed to load property list file ..."];
141       goto done;
142     }
143   }
144   s = [[NSString alloc] initWithData:content encoding:[self stringEncoding]];
145   if (s == nil) {
146     e = [NSException exceptionWithHTTPStatus:500
147                      reason:@"failed to create string from file ..."];
148     goto done;
149   }
150   plist = [[s propertyList] mutableCopy];
151   [s release];
152   if (plist == nil) {
153     e = [NSException exceptionWithHTTPStatus:500
154                      reason:@"failed to create property list from file ..."];
155     goto done;
156   }
157   
158   [self removeSpecialKeysFromRestoreDictionary:plist];
159   
160   self->recordKeys = [[plist allKeys] copy];
161   if (debugOn)
162     [self debugWithFormat:@"taking values of: %@", plist];
163   [self takeValuesFromDictionary:plist];
164   [plist release];
165   
166  done:
167   self->flags.isEdited  = 0;
168   self->flags.isLoading = 0;
169   return e;
170 }
171
172 - (void)willChange {
173   if (!self->flags.isLoading)
174     self->flags.isEdited = 1;
175 }
176 - (BOOL)isRestored {
177   return self->flags.isLoaded ? YES : NO;
178 }
179
180 - (NSException *)saveObject {
181   NSMutableDictionary *d;
182   NSException  *e;
183   NSString     *s;
184   NSData       *content;
185   id           fm;
186   
187   e = (self->flags.isNew)
188     ? [self validateForInsert]
189     : [self validateForSave];
190   if (e) return e;
191   
192   if (!self->flags.isNew) {
193     if ((e = [self restoreObject]))
194       return e;
195   }
196   
197   d = (self->recordKeys)
198     ? [[self valuesForKeys:self->recordKeys] mutableCopy]
199     : [self->record mutableCopy];
200   
201   if (d == nil) {
202     [self logWithFormat:@"got no dict to save ..."];
203     return [NSException exceptionWithHTTPStatus:500
204                         reason:@"got no record to save ..."];
205   }
206   
207   [self addSpecialKeysToSaveDictionary:d];
208   
209   s = [d description];
210   [d release];
211   
212   content = [s dataUsingEncoding:[self stringEncoding]];
213   
214   fm = [self fileManager];
215   
216   if (![fm writeContents:content atPath:[self storagePath]]) {
217     [self logWithFormat:@"failed to update file: %@", [self storagePath]];
218     
219     if (([fm respondsToSelector:@selector(lastException)]))
220       return [fm lastException];
221     else {
222       return [NSException exceptionWithHTTPStatus:500
223                           reason:@"failed to update property list file ..."];
224     }
225   }
226   
227   self->flags.isNew = 0;
228   return nil;
229 }
230
231 /* KVC */
232
233 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
234   id oldValue;
235   
236   if ((oldValue = [self->record objectForKey:_key]) == nil) {
237     if (_value == nil) return;
238   }
239   else if (![_value isNotNull]) {
240     [self willChange];
241     [self->record removeObjectForKey:_key];
242     return;
243   }
244   else if (oldValue == _value) {
245     return;
246   }
247   else if ([oldValue isEqual:_value])
248     return;
249   
250   [self willChange];
251   
252   if (self->record == nil)
253     self->record = [[NSMutableDictionary alloc] initWithCapacity:16];
254   
255   if (!self->flags.isLoading && debugOn)
256     [self debugWithFormat:@"set unbound key: %@", _key];
257   
258   if (![self->recordKeys containsObject:_key]) {
259     NSMutableArray *rk;
260     
261     rk = [self->recordKeys mutableCopy];
262     [rk addObject:_key];
263     [self->recordKeys release];
264     self->recordKeys = rk;
265   }
266   
267   [self->record setObject:_value?_value:@"" forKey:_key];
268 }
269
270 - (BOOL)isStoredKey:(NSString *)_key {
271   /* says whether we need to restore the object to access the key */
272   if ([_key hasPrefix:@"NS"]) {
273     if ([_key isEqualToString:@"NSFileSubject"])
274       return YES;
275     return NO;
276   }
277   return YES;
278 }
279
280 - (void)takeValue:(id)_value forKey:(NSString *)_name {
281   if (!self->flags.isLoaded && !self->flags.isLoading) {
282     if ([self isStoredKey:_name])
283       [[self restoreObject] raise];
284   }
285   
286   [super takeValue:_value forKey:_name];
287 }
288
289 - (id)valueForKey:(NSString *)_name {
290   id v = nil;
291   
292   if ([_name hasPrefix:@"NS"]) {
293     if ([_name isEqualToString:@"NSFileSize"])
294       v = [self davContentLength];
295     else if ([_name isEqualToString:@"NSFileSubject"])
296       v = [self davDisplayName];
297     else
298       /* this implies that stored keys never begin with NS ! (good ?) */
299       v = [super valueForKey:_name];
300   }
301   else if ([self isStoredKey:_name]) {
302     if ((v = [self restoreObject]))
303       /* v is the restoration exception, do not want to raise */;
304     else if ((v = [self->record objectForKey:_name]))
305       /* a record value */;
306     else
307       /* stored-key doesn't say *where* it is stored ! */
308       v = [super valueForKey:_name];
309   }
310   else
311     v = [super valueForKey:_name];
312   
313   return v;
314 }
315
316 /* operations */
317
318 - (id)GETAction:(WOContext *)_ctx {
319   NSException *e;
320   
321   if ((e = [self restoreObject]))
322     return e;
323   
324   /* let the renderer deal with our representation ... */
325   return self;
326 }
327
328 - (id)PUTAction:(WOContext *)_ctx {
329   return [NSException exceptionWithHTTPStatus:405 /* method not allowed */
330                       reason:@"HTTP PUT not yet allowed on plist objects"];
331 }
332
333 /* WebDAV support */
334
335 - (NSString *)davDisplayName {
336   return [[self nameInContainer] stringByDeletingPathExtension];
337 }
338 - (id)davContentLength {
339   static NSNumber *zero = nil;
340   if (zero == nil) zero = [[NSNumber numberWithInt:0] retain];
341   return zero;
342 }
343
344 - (NSException *)davSetProperties:(NSDictionary *)_setProps
345   removePropertiesNamed:(NSArray *)_delProps 
346   inContext:(id)_ctx
347 {
348   NSException *e;
349   
350   if (debugOn)
351     [self debugWithFormat:@"patch: %@, del: %@", _setProps, _delProps];
352   
353   if ((e = [self restoreObject]))
354     return e;
355   
356   if ([_setProps count] > 0)
357     [self takeValuesFromDictionary:_setProps];
358   
359   if ([_delProps count] > 0) {
360     NSMutableArray *rk;
361     
362     [self->record removeObjectsForKeys:_delProps];
363     rk = [self->recordKeys mutableCopy];
364     [rk removeObjectsInArray:_delProps];
365     [self->recordKeys release];
366     self->recordKeys = rk;
367   }
368   
369   if ((e = [self saveObject])) {
370     [self logWithFormat:@"update failed ..."];
371     return e;
372   }
373   
374   return nil;
375 }
376
377 /* factory */
378
379 + (id)instantiateInFactoryContext:(OFSFactoryContext *)_ctx {
380   /* look into plist for class */
381   NSException  *e;
382   OFSPropertyListObject *object;
383   SoClass      *clazz;
384   
385   if ([_ctx isNewObject]) {
386     /* create a new object in the storage */
387     clazz = [self soClass];
388     
389     /* instantiate */
390     if (debugOn) {
391       [self debugWithFormat:@"instantiate child %@ from class %@",
392             [_ctx nameInContainer], clazz];
393     }
394     
395     object = [clazz instantiateObject];
396     [object takeStorageInfoFromContext:_ctx];
397     
398     if ([object isKindOfClass:[OFSPropertyListObject class]])
399       object->flags.isNew = 1;
400
401     if ((e = [object saveObject])) {
402       [self debugWithFormat:@"  save failed: %@", e];
403       return e;
404     }
405   }
406   else {
407     /* restore object from storage */
408     NSDictionary *plist;
409     NSData       *content;
410     NSString     *string;
411     NSString     *className;
412     
413     content = [[_ctx fileManager] contentsAtPath:[_ctx storagePath]];
414     if (content == nil)
415       /* hm, file doesn't exist ? */
416       return [super instantiateInFactoryContext:_ctx];
417     
418     /* parse the existing plist file */
419     
420     string = [[NSString alloc] initWithData:content
421                                encoding:[NSString defaultCStringEncoding]];
422     if (string == nil) {
423       [self logWithFormat:@"could not make string for stored data."];
424       return [NSException exceptionWithHTTPStatus:500
425                         reason:@"stored property list is corrupted"];
426     }
427     
428     if ((plist = [string propertyList]) == nil) {
429       [string release];
430       [self logWithFormat:@"could not make plist for stored data."];
431       return [NSException exceptionWithHTTPStatus:500
432                         reason:
433                           @"stored property list is corrupted "
434                           @"(not in plist format)"];
435     }
436     [string release];
437   
438     /* lookup the classname in plist */
439     
440     className = [plist objectForKey:@"SoClassName"];
441     if ([className length] == 0)
442       /* no special class assigned, use default */
443       clazz = [self soClass];
444     else {
445       clazz = [[SoClassRegistry sharedClassRegistry] soClassWithName:className];
446       if (clazz == nil) {
447         [self logWithFormat:@"did not find SoClass: %@", className];
448         return nil;
449       }
450     }
451     
452     /* instantiate */
453     
454     if (debugOn) {
455       [self debugWithFormat:@"instantiate child %@ from class %@",
456             [_ctx nameInContainer], clazz];
457     }
458     
459     object = [clazz instantiateObject];
460     [object takeStorageInfoFromContext:_ctx];
461     
462     /* restore */
463     
464     if (debugOn) {
465       [self debugWithFormat:@"restore child %@: %@",
466             [_ctx nameInContainer], object];
467     }
468     
469     if ((e = [object restoreObject])) {
470       [self debugWithFormat:@"  restore failed: %@", e];
471       return e;
472     }
473   }  
474   return object;
475 }
476
477 /* debugging */
478
479 - (BOOL)isDebuggingEnabled {
480   return debugOn ? YES : NO;
481 }
482
483 @end /* OFSPropertyListObject */
484
485 @implementation OFSPropertyListObjectClassDescription
486
487 - (void)dealloc {
488   [self->object release];
489   [super dealloc];
490 }
491
492 - (NSArray *)attributeKeys {
493   return [self->object attributeKeys];
494 }
495
496 @end /* OFSPropertyListObjectClassDescription */