root/trunk/Update Checker Sources/RSS.m

Revision 269, 17.1 kB (checked in by durin42, 2 years ago)

Initial non-working updater app - it gets as far as downloading an update, but
it doesn't know how to unarchive yet. I've grabbed a bunch of sources from
Sparkle (duh from the filenames) but this is all we'll use from there, as from
here on out our mission is quite different from theirs. I've not yet tested
the appcast support very thoroughly, but it seems to be working in my initial
tests. I need to change it from NSUserDefaults to CFPreferencesCopyAppValue,
and fix the appcast URL in the Info.plist (note to other devs: the current URL
resolves to a nonroutable IP - you've been warned!).
I'll do unarchiving in a bit - but first I need to think about some things. I
think the solution Graham offered of using a shell script and exec() will be best.

  • Property svn:executable set to *
Line 
1 /*
2
3 BSD License
4
5 Copyright (c) 2002, Brent Simmons
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
10
11 *       Redistributions of source code must retain the above copyright notice,
12         this list of conditions and the following disclaimer.
13 *       Redistributions in binary form must reproduce the above copyright notice,
14         this list of conditions and the following disclaimer in the documentation
15         and/or other materials provided with the distribution.
16 *       Neither the name of ranchero.com or Brent Simmons nor the names of its
17         contributors may be used to endorse or promote products derived
18         from this software without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
24 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
25 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26 OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31
32 */
33
34 /*
35         RSS.m
36         A class for reading RSS feeds.
37
38         Created by Brent Simmons on Wed Apr 17 2002.
39         Copyright (c) 2002 Brent Simmons. All rights reserved.
40 */
41
42
43 #import "RSS.h"
44
45 // This comparator function is used to sort the RSS items by their published date.
46 int compareNewsItems(id item1, id item2, void *context)
47 {
48         // We compare item2 with item1 instead of the other way 'round because we want descending, not ascending. Bit of a hack.
49         return [(NSDate *)[NSDate dateWithNaturalLanguageString:[item2 objectForKey:@"pubDate"]] compare:(NSDate *)[NSDate dateWithNaturalLanguageString:[item1 objectForKey:@"pubDate"]]];
50 }
51
52 @implementation RSS
53
54
55 #define titleKey @"title"
56 #define linkKey @"link"
57 #define descriptionKey @"description"
58
59
60 /*Public interface*/
61
62 - (NSDictionary *)newestItem
63 {
64         // The news items are already sorted by published date, descending.
65         return [[self newsItems] objectAtIndex:0];
66 }
67
68 - (RSS *) initWithTitle: (NSString *) title andDescription: (NSString *) description {
69        
70         /*
71         Create an empty feed. Useful for synthetic feeds.
72         */
73        
74         NSMutableDictionary *header;
75
76         flRdf = NO;
77        
78         header = [NSMutableDictionary dictionaryWithCapacity: 2];
79        
80         [header setObject: title forKey: titleKey];
81        
82         [header setObject: description forKey: descriptionKey];
83        
84         headerItems = (NSDictionary *) [header copy];
85        
86         newsItems = [[NSMutableArray alloc] initWithCapacity: 0];
87        
88         version = [[NSString alloc] initWithString: @"synthetic"];
89        
90         return (self);
91         } /*initWithTitle*/
92        
93        
94 - (RSS *) initWithData: (NSData *) rssData normalize: (BOOL) fl {
95        
96         CFXMLTreeRef tree;
97        
98         flRdf = NO;
99        
100         normalize = fl;
101        
102         NS_DURING
103        
104                 tree = CFXMLTreeCreateFromData (kCFAllocatorDefault, (CFDataRef) rssData,
105                         NULL,  kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion);
106        
107         NS_HANDLER
108                
109                 tree = nil;
110        
111         NS_ENDHANDLER
112        
113         if (tree == nil) {
114                
115                 /*If there was a problem parsing the RSS file,
116                 raise an exception.*/
117        
118                 NSException *exception = [NSException exceptionWithName: @"RSSParseFailed"
119                         reason: @"The XML parser could not parse the RSS data." userInfo: nil];
120                
121                 [exception raise];
122                 } /*if*/
123        
124         [self createheaderdictionary: tree];
125        
126         [self createitemsarray: tree];
127        
128         [self setversionstring: tree];
129        
130         CFRelease (tree);
131        
132         return (self);
133         } /*initWithData*/
134
135
136 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl
137 {
138         return [self initWithURL: url normalize: fl userAgent: nil];
139 }
140
141          
142        
143 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl userAgent: (NSString*)userAgent
144 {
145         NSData *rssData;
146
147         NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url cachePolicy: NSURLRequestReloadIgnoringCacheData
148                                                                                 timeoutInterval: 30.0];
149         if (userAgent)
150                 [request setValue: userAgent forHTTPHeaderField: @"User-Agent"];
151                        
152         NSURLResponse *response=0;
153         NSError *error=0;
154
155         rssData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
156        
157         if (rssData == nil)
158         {
159                 NSException *exception = [NSException exceptionWithName: @"RSSDownloadFailed"
160                                                                                                                  reason: [error localizedFailureReason] userInfo: [error userInfo] ];
161                 [exception raise];
162         }
163        
164         return [self initWithData: rssData normalize: fl];     
165 } /*initWithUrl*/
166
167
168 - (NSDictionary *) headerItems {
169        
170         return (headerItems);
171         } /*headerItems*/
172
173
174 - (NSMutableArray *) newsItems {
175        
176         return (newsItems);
177         } /*newsItems*/
178
179
180 - (NSString *) version {
181        
182         return (version);
183         } /*version*/
184
185
186 - (void) dealloc {
187                
188         [headerItems release];
189
190         [newsItems release];
191        
192         [version release];
193         [super dealloc];
194         } /*dealloc*/
195
196
197
198 /*Private methods. Don't call these: they may change.*/
199
200
201 - (void) createheaderdictionary: (CFXMLTreeRef) tree {
202        
203         CFXMLTreeRef channelTree, childTree;
204         CFXMLNodeRef childNode;
205         int childCount, i;
206         NSString *childName;
207         NSMutableDictionary *headerItemsMutable;
208        
209         channelTree = [self getchanneltree: tree];
210        
211         if (channelTree == nil) {
212        
213                 NSException *exception = [NSException exceptionWithName: @"RSSCreateHeaderDictionaryFailed"
214                         reason: @"Couldn't find the channel tree." userInfo: nil];
215
216                 [exception raise];
217                 } /*if*/
218
219         childCount = CFTreeGetChildCount (channelTree);
220        
221         headerItemsMutable = [NSMutableDictionary dictionaryWithCapacity: childCount];
222                
223         for (i = 0; i < childCount; i++) {
224                
225                 childTree = CFTreeGetChildAtIndex (channelTree, i);
226                
227                 childNode = CFXMLTreeGetNode (childTree);
228                
229                 childName = (NSString *) CFXMLNodeGetString (childNode);
230                
231                 if ([childName hasPrefix: @"rss:"])
232                         childName = [childName substringFromIndex: 4];
233                
234                 if ([childName isEqualToString: @"item"])
235                         break;
236                
237                 if ([childName isEqualTo: @"image"])
238                         [self flattenimagechildren: childTree into: headerItemsMutable];
239
240                 [headerItemsMutable setObject: [self getelementvalue: childTree] forKey: childName];
241                 } /*for*/
242        
243         headerItems = [headerItemsMutable copy];
244         } /*initheaderdictionary*/
245
246
247 - (void) createitemsarray: (CFXMLTreeRef) tree {
248        
249         CFXMLTreeRef channelTree, childTree, itemTree;
250         CFXMLNodeRef childNode, itemNode;
251         NSString *childName;
252         NSString *itemName, *itemValue;
253         int childCount, itemChildCount, i, j;
254         NSMutableDictionary *itemDictionaryMutable;
255         NSMutableArray *itemsArrayMutable;
256        
257         if (flRdf)
258                 channelTree = [self getnamedtree: tree name: @"rdf:RDF"];
259         else
260                 channelTree = [self getchanneltree: tree];
261        
262         if (channelTree == nil) {
263                
264                 NSException *exception = [NSException exceptionWithName: @"RSSCreateItemsArrayFailed"
265                         reason: @"Couldn't find the news items." userInfo: nil];
266
267                 [exception raise];
268                 } /*if*/
269        
270         childCount = CFTreeGetChildCount (channelTree);
271        
272         itemsArrayMutable = [NSMutableArray arrayWithCapacity: childCount];
273        
274         for (i = 0; i < childCount; i++) {
275                
276                 childTree = CFTreeGetChildAtIndex (channelTree, i);
277                
278                 childNode = CFXMLTreeGetNode (childTree);
279                
280                 childName = (NSString *) CFXMLNodeGetString (childNode);
281                
282                 if ([childName hasPrefix: @"rss:"])
283                         childName = [childName substringFromIndex: 4];
284                
285                 if (![childName isEqualToString: @"item"])
286                         continue;
287                
288                 itemChildCount = CFTreeGetChildCount (childTree);
289                
290                 itemDictionaryMutable = [NSMutableDictionary dictionaryWithCapacity: itemChildCount];
291                
292                 for (j = 0; j < itemChildCount; j++) {
293                                
294                         itemTree = CFTreeGetChildAtIndex (childTree, j);
295                        
296                         itemNode = CFXMLTreeGetNode (itemTree);
297                        
298                         itemName = (NSString *) CFXMLNodeGetString (itemNode);
299                        
300                         if ([itemName hasPrefix: @"rss:"])
301                                 itemName = [itemName substringFromIndex: 4];
302                        
303                         if ([itemName isEqualTo:@"enclosure"])
304                         {
305                                 // Hack to add attributes to the dictionary in addition to children. (AMM)
306                                 const CFXMLElementInfo *websiteInfo = CFXMLNodeGetInfoPtr(itemNode);
307                                 NSMutableDictionary *enclosureDictionary = [NSMutableDictionary dictionary];
308                                 id keyEnumerator = [(NSDictionary *)websiteInfo->attributes keyEnumerator], current;
309                                 while ((current = [keyEnumerator nextObject]))
310                                 {
311                                         [enclosureDictionary setObject:[(NSDictionary *)websiteInfo->attributes objectForKey:current] forKey:current];
312                                 }
313                                 [itemDictionaryMutable setObject: enclosureDictionary forKey: itemName];
314                                 continue;
315                         }
316                        
317                         itemValue = [self getelementvalue: itemTree];
318                        
319                         if ([itemName isEqualTo: @"source"])
320                                 [self flattensourceattributes: itemNode into: itemDictionaryMutable];
321                        
322                         [itemDictionaryMutable setObject: itemValue forKey: itemName];
323                         } /*for*/
324                
325                 if (normalize)
326                         [self normalizeRSSItem: itemDictionaryMutable];
327                
328                 [itemsArrayMutable addObject: itemDictionaryMutable];
329                 } /*for*/
330        
331         // Sort the news items by published date, descending.
332         newsItems = [[itemsArrayMutable sortedArrayUsingFunction:compareNewsItems context:NULL] retain];
333         } /*createitemsarray*/
334
335
336 - (void) setversionstring: (CFXMLTreeRef) tree {
337        
338         CFXMLTreeRef rssTree;
339         const CFXMLElementInfo *elementInfo;
340         CFXMLNodeRef node;
341        
342         if (flRdf) {
343                
344                 version = [[NSString alloc] initWithString: @"rdf"];
345                
346                 return;
347                 } /*if*/
348                
349         rssTree = [self getnamedtree: tree name: @"rss"];
350        
351         node = CFXMLTreeGetNode (rssTree);
352
353         elementInfo = CFXMLNodeGetInfoPtr (node);
354
355         version = [[NSString alloc] initWithString: [(NSDictionary *) (*elementInfo).attributes objectForKey: @"version"]];     
356         } /*setversionstring*/
357        
358
359 - (void) flattenimagechildren: (CFXMLTreeRef) tree into: (NSMutableDictionary *) dictionary {
360        
361         int childCount = CFTreeGetChildCount (tree);
362         int i = 0;
363         CFXMLTreeRef childTree;
364         CFXMLNodeRef childNode;
365         NSString *childName, *childValue, *keyName;
366        
367         if (childCount < 1)
368                 return;
369                
370         for (i = 0; i < childCount; i++) {
371                
372                 childTree = CFTreeGetChildAtIndex (tree, i);
373                
374                 childNode = CFXMLTreeGetNode (childTree);
375                
376                 childName = (NSString *) CFXMLNodeGetString (childNode);
377                
378                 if ([childName hasPrefix: @"rss:"])
379                         childName = [childName substringFromIndex: 4];
380                
381                 childValue = [self getelementvalue: childTree];
382                
383                 keyName = [NSString stringWithFormat: @"image%@", childName];
384                
385                 [dictionary setObject: childValue forKey: keyName];
386                 } /*for*/
387         } /*flattenimagechildren*/
388
389
390 - (void) flattensourceattributes: (CFXMLNodeRef) node into: (NSMutableDictionary *) dictionary {
391        
392         const CFXMLElementInfo *elementInfo;
393         NSString *sourceHomeUrl, *sourceRssUrl;
394
395         elementInfo = CFXMLNodeGetInfoPtr (node);
396        
397         sourceHomeUrl = [(NSDictionary *) (*elementInfo).attributes objectForKey: @"homeUrl"];
398        
399         sourceRssUrl = [(NSDictionary *) (*elementInfo).attributes objectForKey: @"url"];
400        
401         if (sourceHomeUrl != nil)
402                 [dictionary setObject: sourceHomeUrl forKey: @"sourceHomeUrl"];
403        
404         if (sourceRssUrl != nil)
405                 [dictionary setObject: sourceRssUrl forKey: @"sourceRssUrl"];
406         } /*flattensourceattributes*/
407        
408        
409 - (CFXMLTreeRef) getchanneltree: (CFXMLTreeRef) tree {
410        
411         CFXMLTreeRef rssTree, channelTree;
412        
413         rssTree = [self getnamedtree: tree name: @"rss"];
414        
415         if (rssTree == nil) { /*It might be "rdf:RDF" instead, a 1.0 or greater feed.*/
416        
417                 rssTree = [self getnamedtree: tree name: @"rdf:RDF"];
418                
419                 if (rssTree != nil)             
420                         flRdf = YES; /*This info will be needed later when creating the items array.*/
421                 } /*if*/
422        
423         if (rssTree == nil)
424                 return (nil);
425        
426         channelTree = [self getnamedtree: rssTree name: @"channel"];
427        
428         if (channelTree == nil)
429                 channelTree = [self getnamedtree: rssTree name: @"rss:channel"];
430        
431         return (channelTree);
432         } /*getchanneltree*/
433
434
435 - (CFXMLTreeRef) getnamedtree: (CFXMLTreeRef) currentTree name: (NSString *) name {
436        
437         int childCount, i;
438         CFXMLNodeRef xmlNode;
439         CFXMLTreeRef xmlTreeNode;
440         NSString *itemName;
441        
442         childCount = CFTreeGetChildCount (currentTree);
443        
444         for (i = childCount - 1; i >= 0; i--) {
445                
446                 xmlTreeNode = CFTreeGetChildAtIndex (currentTree, i);
447                
448                 xmlNode = CFXMLTreeGetNode (xmlTreeNode);
449                
450                 itemName = (NSString *) CFXMLNodeGetString (xmlNode);
451                
452                 if ([itemName isEqualToString: name])
453                         return (xmlTreeNode);
454                 } /*for*/
455        
456         return (nil);
457         } /*getnamedtree*/
458
459
460 - (void) normalizeRSSItem: (NSMutableDictionary *) rssItem {
461        
462         /*
463         Make sure item, link, and description are present and have
464         reasonable values. Description and link may be "".
465         Also trim white space, remove HTML when appropriate.
466         */
467        
468         NSString *description, *link, *title;
469         BOOL nilDescription = NO;
470        
471         /*Description*/
472        
473         description = [rssItem objectForKey: descriptionKey];
474        
475         if (description == nil) {
476                
477                 description = @"";
478                
479                 nilDescription = YES;
480                 } /*if*/
481        
482         description = [description trimWhiteSpace];
483        
484         if ([description isEqualTo: @""])
485                 nilDescription = YES;
486        
487         [rssItem setObject: description forKey: descriptionKey];
488        
489         /*Link*/
490        
491         link = [rssItem objectForKey: linkKey];
492        
493         if ([NSString stringIsEmpty: link]) {
494                
495                 /*Try to get a URL from the description.*/
496                
497                 if (!nilDescription) {
498                                        
499                         NSArray *stringComponents = [description componentsSeparatedByString: @"href=\""];
500                        
501                         if ([stringComponents count] > 1) {
502                                                        
503                                 link = [stringComponents objectAtIndex: 1];
504                        
505                                 stringComponents = [link componentsSeparatedByString: @"\""];
506
507                                 link = [stringComponents objectAtIndex: 0];                     
508                                 } /*if*/                               
509                         } /*if*/
510                 } /*if*/
511        
512         if (link == nil)
513                 link = @"";
514        
515         link = [link trimWhiteSpace];
516        
517         [rssItem setObject: link forKey: linkKey];
518        
519         /*Title*/
520        
521         title = [rssItem objectForKey: titleKey];
522                
523         if (title != nil) {
524        
525                 title = [title stripHTML];
526                
527                 title = [title trimWhiteSpace];
528                 } /*if*/
529        
530         if ([NSString stringIsEmpty: title]) {
531                
532                 /*Grab a title from the description.*/
533                
534                 if (!nilDescription) {
535
536                         NSArray *stringComponents = [description componentsSeparatedByString: @">"];
537                        
538                         if ([stringComponents count] > 1) {
539                        
540                                 title = [stringComponents objectAtIndex: 1];
541                                
542                                 stringComponents = [title componentsSeparatedByString: @"<"];
543        
544                                 title = [stringComponents objectAtIndex: 0];
545                                
546                                 title = [title stripHTML];
547                                
548                                 title = [title trimWhiteSpace];
549                                 } /*if*/
550                        
551                         if ([NSString stringIsEmpty: title]) { /*use first part of description*/
552                                
553                                 NSString *shortTitle = [[[description stripHTML] trimWhiteSpace] ellipsizeAfterNWords: 5];
554
555                                 shortTitle = [shortTitle trimWhiteSpace];
556                                
557                                 title = [NSString stringWithFormat: @"%@...", shortTitle];                             
558                                 } /*else*/                             
559                         } /*if*/
560                        
561                 title = [title stripHTML];
562        
563                 title = [title trimWhiteSpace];
564        
565                 if ([NSString stringIsEmpty: title])
566                         title = @"Untitled";   
567                 } /*if*/
568                
569         [rssItem setObject: title forKey: titleKey];
570        
571         /*dangerousmeta case: super-long title with no description*/
572        
573         if ((nilDescription) && ([title length] > 50)) {
574                                                
575                 NSString *shortTitle = [[[title stripHTML] trimWhiteSpace] ellipsizeAfterNWords: 7];
576                                
577                 description = [[title copy] autorelease];
578                
579                 [rssItem setObject: description forKey: descriptionKey];
580                
581                 title = [NSString stringWithFormat: @"%@...", shortTitle];                             
582                
583                 [rssItem setObject: title forKey: titleKey];
584                 } /*if*/
585
586         { /*deal with entities*/
587                
588                 const char *tempcstring;
589                 NSAttributedString *s = nil;
590                 NSString *convertedTitle = nil;
591                 NSArray *stringComponents;
592                
593                 stringComponents = [title componentsSeparatedByString: @"&"];
594                
595                 if ([stringComponents count] > 1) {
596                        
597                         stringComponents = [title componentsSeparatedByString: @";"];
598                        
599                         if ([stringComponents count] > 1) {
600                        
601                                 int len;
602                                
603                                 tempcstring = [title UTF8String];
604                                
605                                 len = strlen (tempcstring);
606                                
607                                 if (len > 0) {
608                                
609                                         s = [[NSAttributedString alloc]
610                                                 initWithHTML: [NSData dataWithBytes: tempcstring length: strlen (tempcstring)]
611                                                 documentAttributes: (NSDictionary **) NULL];
612                
613                                         convertedTitle = [s string];
614                                
615                                         [s autorelease];
616                                                                        
617                                         convertedTitle = [convertedTitle stripHTML];
618                                
619                                         convertedTitle = [convertedTitle trimWhiteSpace];                               
620                                         } /*if*/
621                                
622                                 if ([NSString stringIsEmpty: convertedTitle])
623                                         convertedTitle = @"Untitled";
624                                
625                                 [rssItem setObject: convertedTitle forKey: @"convertedTitle"];
626                                 } /*if*/
627                         } /*if*/
628                 } /*deal with entities*/
629         } /*normalizeRSSItem*/
630
631
632 - (NSString *) getelementvalue: (CFXMLTreeRef) tree {
633        
634         CFXMLNodeRef node;
635         CFXMLTreeRef itemTree;
636         int childCount, ix;
637         NSMutableString *valueMutable;
638         NSString *value;
639         NSString *name;
640        
641         childCount = CFTreeGetChildCount (tree);
642        
643         valueMutable = [[NSMutableString alloc] init];
644
645         for (ix = 0; ix < childCount; ix++) {
646                
647                 itemTree = CFTreeGetChildAtIndex (tree, ix);
648                
649                 node = CFXMLTreeGetNode (itemTree);
650                
651                 name = (NSString *) CFXMLNodeGetString (node);
652                
653                 if (name != nil) {
654                
655                         if (CFXMLNodeGetTypeCode (node) == kCFXMLNodeTypeEntityReference) {
656                                
657                                 if ([name isEqualTo: @"lt"])
658                                         name = @"<";
659
660                                 else if ([name isEqualTo: @"gt"])
661                                         name = @">";
662                                
663                                 else if ([name isEqualTo: @"quot"])
664                                         name = @"\"";
665                                
666                                 else if ([name isEqualTo: @"amp"])
667                                         name = @"&";
668                                
669                                 else if ([name isEqualTo: @"rsquo"])
670                                         name = @"\"";
671                                
672                                 else if ([name isEqualTo: @"lsquo"])
673                                         name = @"\"";
674                                
675                                 else if ([name isEqualTo: @"apos"])
676                                         name = @"'";                           
677                                 else
678                                         name = [NSString stringWithFormat: @"&%@;", name];
679                                 } /*if*/
680                                                
681                         [valueMutable appendString: name];
682                         } /*if*/
683                 } /*for*/
684        
685         value = [valueMutable copy];
686        
687         [valueMutable autorelease];
688
689         return ([value autorelease]);
690         } /*getelementvalue*/
691
692 @end
Note: See TracBrowser for help on using the browser.