source: trunk/Sparkle/SUAppcast.m @ 1046

Revision 630, 7.5 KB checked in by speck, 3 years ago (diff)

Sparkle update with latest bug fixes.

Line 
1//
2//  SUAppcast.m
3//  Sparkle
4//
5//  Created by Andy Matuschak on 3/12/06.
6//  Copyright 2006 Andy Matuschak. All rights reserved.
7//
8
9#import "Sparkle.h"
10#import "SUAppcast.h"
11
12@interface SUAppcast (Private)
13- (void)reportError:(NSError *)error;
14- (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes;
15@end
16
17@implementation SUAppcast
18
19- (void)dealloc
20{
21    [items release];
22    [userAgentString release];
23    [super dealloc];
24}
25
26- (NSArray *)items
27{
28    return items;
29}
30
31- (void)fetchAppcastFromURL:(NSURL *)url
32{
33    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
34    if (userAgentString)
35        [request setValue:userAgentString forHTTPHeaderField:@"User-Agent"];
36           
37    NSURLDownload *download = [[[NSURLDownload alloc] initWithRequest:request delegate:self] autorelease];
38    CFRetain(download);
39}
40
41- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename
42{
43    NSString *destinationFilename = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
44    [download setDestination:destinationFilename allowOverwrite:NO];
45}
46
47- (void)download:(NSURLDownload *)download didCreateDestination:(NSString *)path
48{
49    [downloadFilename release];
50    downloadFilename = [path copy];
51}
52
53- (void)downloadDidFinish:(NSURLDownload *)download
54{
55    CFRelease(download);
56   
57    NSError *error = nil;
58    NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:[NSURL fileURLWithPath:downloadFilename] options:0 error:&error];
59    BOOL failed = NO;
60    NSArray *xmlItems = nil;
61    NSMutableArray *appcastItems = [NSMutableArray array];
62   
63    [[NSFileManager defaultManager] removeFileAtPath:downloadFilename handler:nil];
64    [downloadFilename release];
65    downloadFilename = nil;
66   
67    if (nil == document)
68    {
69        failed = YES;
70    }
71    else
72    {
73        xmlItems = [document nodesForXPath:@"/rss/channel/item" error:&error];
74        if (nil == xmlItems)
75        {
76            failed = YES;
77        }
78    }
79   
80    if (failed == NO)
81    {
82       
83        NSEnumerator *nodeEnum = [xmlItems objectEnumerator];
84        NSXMLNode *node;
85        NSMutableDictionary *nodesDict = [NSMutableDictionary dictionary];
86        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
87       
88        while (failed == NO && (node = [nodeEnum nextObject]))
89        {
90            // First, we'll "index" all the first-level children of this appcast item so we can pick them out by language later.
91            if ([[node children] count])
92            {
93                node = [node childAtIndex:0];
94                while (nil != node)
95                {
96                    NSString *name = [node name];
97                    if (name)
98                    {
99                        NSMutableArray *nodes = [nodesDict objectForKey:name];
100                        if (nodes == nil)
101                        {
102                            nodes = [NSMutableArray array];
103                            [nodesDict setObject:nodes forKey:name];
104                        }
105                        [nodes addObject:node];
106                    }
107                    node = [node nextSibling];
108                }
109            }
110           
111            NSEnumerator *nameEnum = [nodesDict keyEnumerator];
112            NSString *name;
113            while ((name = [nameEnum nextObject]))
114            {
115                node = [self bestNodeInNodes:[nodesDict objectForKey:name]];
116                if ([name isEqualToString:@"enclosure"])
117                {
118                    // enclosure is flattened as a separate dictionary for some reason
119                    NSEnumerator *attributeEnum = [[(NSXMLElement *)node attributes] objectEnumerator];
120                    NSXMLNode *attribute;
121                    NSMutableDictionary *encDict = [NSMutableDictionary dictionary];
122                   
123                    while ((attribute = [attributeEnum nextObject]))
124                        [encDict setObject:[attribute stringValue] forKey:[attribute name]];
125                    [dict setObject:encDict forKey:@"enclosure"];
126                   
127                }
128                else if ([name isEqualToString:@"pubDate"])
129                {
130                    // pubDate is expected to be an NSDate by SUAppcastItem, but the RSS class was returning an NSString
131                    NSDate *date = [NSDate dateWithNaturalLanguageString:[node stringValue]];
132                    if (date)
133                        [dict setObject:date forKey:name];
134                }
135                else if (name != nil)
136                {
137                    // add all other values as strings
138                    [dict setObject:[[node stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:name];
139                }
140            }
141           
142            SUAppcastItem *anItem = [[SUAppcastItem alloc] initWithDictionary:dict];
143            if (anItem)
144            {
145                [appcastItems addObject:anItem];
146                [anItem release];
147            }
148            else
149            {
150                NSLog(@"Sparkle Updater: Failed to parse appcast item with appcast dictionary %@!", dict);
151            }
152            [nodesDict removeAllObjects];
153            [dict removeAllObjects];
154        }
155    }
156   
157    [document release];
158   
159    if ([appcastItems count])
160    {
161        NSSortDescriptor *sort = [[[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO] autorelease];
162        [appcastItems sortUsingDescriptors:[NSArray arrayWithObject:sort]];
163        items = [appcastItems copy];
164    }
165   
166    if (failed)
167    {
168        [self reportError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastParseError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while parsing the update feed.", nil), NSLocalizedDescriptionKey, nil]]];
169    }
170    else if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:)])
171    {
172        [delegate appcastDidFinishLoading:self];
173    }
174}
175
176- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
177{
178    CFRelease(download);
179   
180    [[NSFileManager defaultManager] removeFileAtPath:downloadFilename handler:nil];
181    [downloadFilename release];
182    downloadFilename = nil;
183   
184    [self reportError:error];
185}
186
187- (NSURLRequest *)download:(NSURLDownload *)download willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
188{
189    return request;
190}
191
192- (void)reportError:(NSError *)error
193{
194    if ([delegate respondsToSelector:@selector(appcast:failedToLoadWithError:)])
195    {
196        [delegate appcast:self failedToLoadWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]];
197    }
198}
199
200- (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes
201{
202    // We use this method to pick out the localized version of a node when one's available.
203    if ([nodes count] == 1)
204        return [nodes objectAtIndex:0];
205    else if ([nodes count] == 0)
206        return nil;
207   
208    NSEnumerator *nodeEnum = [nodes objectEnumerator];
209    NSXMLElement *node;
210    NSMutableArray *languages = [NSMutableArray array];
211    NSString *lang;
212    NSInteger i;
213    while ((node = [nodeEnum nextObject]))
214    {
215        lang = [[node attributeForName:@"xml:lang"] stringValue];
216        [languages addObject:(lang ?: @"")];
217    }
218    lang = [[NSBundle preferredLocalizationsFromArray:languages] objectAtIndex:0];
219    i = [languages indexOfObject:([languages containsObject:lang] ? lang : @"")];
220    if (i == NSNotFound)
221        i = 0;
222    return [nodes objectAtIndex:i];
223}
224
225- (void)setUserAgentString:(NSString *)uas
226{
227    if (uas != userAgentString)
228    {
229        [userAgentString release];
230        userAgentString = [uas copy];
231    }
232}
233
234- (void)setDelegate:del
235{
236    delegate = del;
237}
238
239@end
Note: See TracBrowser for help on using the repository browser.