/
NewSubscription.m
424 lines (378 loc) · 13.1 KB
/
NewSubscription.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
//
// NewSubscription.m
// Vienna
//
// Created by Steve on 4/23/05.
// Copyright (c) 2004-2005 Steve Palmer. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "NewSubscription.h"
#import "AppController.h"
#import "StringExtensions.h"
#import "RichXMLParser.h"
#import "Preferences.h"
#import "GoogleReader.h"
// Private functions
@interface NewSubscription (Private)
-(void)loadRSSFeedBundle;
-(void)setLinkTitle;
-(void)enableSaveButton;
-(void)enableSubscribeButton;
@end
@implementation NewSubscription
/* initWithDatabase
* Just init the RSS feed class.
*/
-(id)initWithDatabase:(Database *)newDb
{
if ((self = [super init]) != nil)
{
db = newDb;
sourcesDict = nil;
editFolderId = -1;
parentId = MA_Root_Folder;
}
return self;
}
/* newSubscription
* Display the sheet to create a new RSS subscription.
*/
-(void)newSubscription:(NSWindow *)window underParent:(int)itemId initialURL:(NSString *)initialURL
{
[self loadRSSFeedBundle];
// Load a list of sources from the RSSSources property list. The list of sources
// is a dictionary of templates which specify how to create the source URL and a
// display name which acts as the key. This allows us to support additional sources
// without having to write new code.
if (!sourcesDict)
{
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
NSString * pathToPList = [thisBundle pathForResource:@"RSSSources.plist" ofType:@""];
if (pathToPList != nil)
{
sourcesDict = [[NSDictionary dictionaryWithContentsOfFile:pathToPList] retain];
[feedSource removeAllItems];
if (sourcesDict)
{
for (NSString * key in sourcesDict)
[feedSource addItemWithTitle:NSLocalizedString(key, nil)];
[feedSource setEnabled:YES];
[feedSource selectItemWithTitle:@"URL"];
}
}
}
if (!sourcesDict)
[feedSource setEnabled:NO];
// Look on the pasteboard to see if there's an http:// url and, if so, prime the
// URL field with it. A handy shortcut.
if (initialURL != nil)
{
[feedURL setStringValue:initialURL];
[feedSource selectItemWithTitle:@"URL"];
}
else
{
NSData * pboardData = [[NSPasteboard generalPasteboard] dataForType:NSStringPboardType];
[feedURL setStringValue:@""];
if (pboardData != nil)
{
NSString * pasteString = [[[NSString alloc] initWithData:pboardData encoding:NSASCIIStringEncoding] autorelease];
NSString * lowerCasePasteString = [pasteString lowercaseString];
if (lowerCasePasteString != nil && ([lowerCasePasteString hasPrefix:@"http://"] || [lowerCasePasteString hasPrefix:@"https://"] || [lowerCasePasteString hasPrefix:@"feed://"]))
{
[feedURL setStringValue:pasteString];
[feedURL selectText:self];
[feedSource selectItemWithTitle:@"URL"];
}
}
}
// Reset from the last time we used this sheet.
[self enableSubscribeButton];
[self setLinkTitle];
editFolderId = -1;
parentId = itemId;
[newRSSFeedWindow makeFirstResponder:feedURL];
self.googleOptionButton=[[Preferences standardPreferences] prefersGoogleNewSubscription]; //restore from preferences
[NSApp beginSheet:newRSSFeedWindow modalForWindow:window modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
/* didEndSubscriptionEdit
* Notification that the editing is done.
*/
- (void)didEndSubscriptionEdit:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
NSNumber * folderNumber = (NSNumber *)contextInfo;
// Notify any open windows.
[[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_FoldersUpdated" object:folderNumber];
// Now release the folder number.
[folderNumber release];
}
/* editSubscription
* Edit an existing RSS subscription.
*/
-(void)editSubscription:(NSWindow *)window folderId:(int)folderId
{
[self loadRSSFeedBundle];
Folder * folder = [db folderFromID:folderId];
if (folder != nil)
{
[editFeedURL setStringValue:[folder feedURL]];
[self enableSaveButton];
editFolderId = folderId;
// Create a context object which contains the folder ID for the sheet to pass to
// selector which it will call when done. Retain it so it is still around for the
// selector.
NSNumber * folderContext = [[NSNumber numberWithInt:folderId] retain];
// Open the edit sheet.
[NSApp beginSheet:editRSSFeedWindow modalForWindow:window modalDelegate:self didEndSelector:@selector(didEndSubscriptionEdit:returnCode:contextInfo:) contextInfo:folderContext];
}
}
/* loadRSSFeedBundle
* Load the RSS feed bundle if not already.
*/
-(void)loadRSSFeedBundle
{
if (!editRSSFeedWindow || !newRSSFeedWindow)
{
[NSBundle loadNibNamed:@"RSSFeed" owner:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextDidChange:) name:NSControlTextDidChangeNotification object:feedURL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextDidChange2:) name:NSControlTextDidChangeNotification object:editFeedURL];
}
}
/* doSubscribe
* Handle the URL subscription button.
*/
-(IBAction)doSubscribe:(id)sender
{
NSString * feedURLString = [[feedURL stringValue] trim];
// Format the URL based on the selected feed source.
if (sourcesDict != nil)
{
NSMenuItem * feedSourceItem = [feedSource selectedItem];
NSString * key = [feedSourceItem title];
NSDictionary * itemDict = [sourcesDict valueForKey:key];
NSString * linkName = [itemDict valueForKey:@"LinkTemplate"];
if (linkName != nil)
feedURLString = [NSString stringWithFormat:linkName, feedURLString];
}
// Replace feed:// with http:// if necessary
if ([feedURLString hasPrefix:@"feed://"])
feedURLString = [NSString stringWithFormat:@"http://%@", [feedURLString substringFromIndex:7]];
// Create the RSS folder in the database
if ([db folderFromFeedURL:feedURLString] != nil)
{
NSRunAlertPanel(NSLocalizedString(@"Already subscribed title", nil),
NSLocalizedString(@"Already subscribed body", nil),
NSLocalizedString(@"OK", nil), nil, nil);
return;
}
// Validate the subscription, possibly replacing the feedURLString with a real one if
// it originally pointed to a web page.
feedURLString = [self verifyFeedURL:feedURLString];
// Call the controller to create the new subscription.
if ([[Preferences standardPreferences] syncGoogleReader] && [[Preferences standardPreferences] prefersGoogleNewSubscription])
{ //creates in Google
GoogleReader * myGoogle = [GoogleReader sharedManager];
[myGoogle subscribeToFeed:feedURLString];
NSString * folderName = [[db folderFromID:parentId] name];
if (folderName != nil)
[myGoogle setFolder:folderName forFeed:feedURLString folderFlag:TRUE];
[myGoogle loadSubscriptions:nil];
}
else
{ //creates locally
[[NSApp delegate] createNewSubscription:feedURLString underFolder:parentId afterChild:-1];
}
// Close the window
[NSApp endSheet:newRSSFeedWindow];
[newRSSFeedWindow orderOut:self];
}
/* doSave
* Save changes to the RSS feed information.
*/
-(IBAction)doSave:(id)sender
{
NSString * feedURLString = [[editFeedURL stringValue] trim];
// Save the new information to the database
[db setFolderFeedURL:editFolderId newFeedURL:feedURLString];
// Close the window
[NSApp endSheet:editRSSFeedWindow];
[editRSSFeedWindow orderOut:self];
}
/* doSubscribeCancel
* Handle the Cancel button.
*/
-(IBAction)doSubscribeCancel:(id)sender
{
[NSApp endSheet:newRSSFeedWindow];
[newRSSFeedWindow orderOut:self];
}
/* doEditCancel
* Handle the Cancel button.
*/
-(IBAction)doEditCancel:(id)sender
{
[NSApp endSheet:editRSSFeedWindow];
[editRSSFeedWindow orderOut:self];
}
/* doLinkSourceChanged
* Called when the user changes the selection in the popup menu.
*/
-(IBAction)doLinkSourceChanged:(id)sender
{
[self setLinkTitle];
}
@synthesize googleOptionButton;
/* doGoogleOption
* Action called by the Google Reader checkbox
* Memorizes the setting in preferences
*/
-(IBAction)doGoogleOption:(id)sender
{
[[Preferences standardPreferences] setPrefersGoogleNewSubscription:([sender state] == NSOnState)];
}
/* handleTextDidChange [delegate]
* This function is called when the contents of the input field is changed.
* We disable the Subscribe button if the input fields are empty or enable it otherwise.
*/
-(void)handleTextDidChange:(NSNotification *)aNotification
{
[self enableSubscribeButton];
}
/* handleTextDidChange2 [delegate]
* This function is called when the contents of the input field is changed.
* We disable the Save button if the input fields are empty or enable it otherwise.
*/
-(void)handleTextDidChange2:(NSNotification *)aNotification
{
[self enableSaveButton];
}
/* setLinkTitle
* Set the text of the label that prompts for the link based on the source
* that the user selected from the popup menu.
*/
-(void)setLinkTitle
{
NSMenuItem * feedSourceItem = [feedSource selectedItem];
NSString * linkTitleString = nil;
BOOL showButton = NO;
if (feedSourceItem != nil)
{
NSDictionary * itemDict = [sourcesDict valueForKey:[feedSourceItem title]];
if (itemDict != nil)
{
linkTitleString = [itemDict valueForKey:@"LinkName"];
showButton = [itemDict valueForKey:@"SiteHomePage"] != nil;
}
}
if (linkTitleString == nil)
linkTitleString = @"Link";
[linkTitle setStringValue:[NSString stringWithFormat:@"%@:", NSLocalizedString(linkTitleString, nil)]];
[siteHomePageButton setHidden:!showButton];
}
/* doShowSiteHomePage
*/
-(void)doShowSiteHomePage:(id)sender
{
NSMenuItem * feedSourceItem = [feedSource selectedItem];
if (feedSourceItem != nil)
{
NSDictionary * itemDict = [sourcesDict valueForKey:[feedSourceItem title]];
if (itemDict != nil)
{
NSString * siteHomePageURL = [itemDict valueForKey:@"SiteHomePage"];
NSURL * url = [[NSURL alloc] initWithString:siteHomePageURL];
[[NSWorkspace sharedWorkspace] openURL:url];
[url release];
}
}
}
/* enableSubscribeButton
* Enable or disable the Subscribe button depending on whether or not there is a non-blank
* string in the input fields.
*/
-(void)enableSubscribeButton
{
NSString * feedURLString = [feedURL stringValue];
[subscribeButton setEnabled:![feedURLString isBlank]];
}
/* enableSaveButton
* Enable or disable the Save button depending on whether or not there is a non-blank
* string in the input fields.
*/
-(void)enableSaveButton
{
NSString * feedURLString = [editFeedURL stringValue];
[saveButton setEnabled:![feedURLString isBlank]];
}
/* verifyFeedURL
* Verify the specified URL. This is the auto-discovery phase that is described at
* http://diveintomark.org/archives/2002/08/15/ultraliberal_rss_locator
*
* Basically we examine the data at the specified URL and if it is an RSS feed
* then OK. Otherwise if it looks like an HTML page, we scan for links in the
* page text.
*/
-(NSString *)verifyFeedURL:(NSString *)feedURLString
{
NSString * urlString = [[feedURLString trim] lowercaseString];
// If the URL starts with feed or ends with a feed extension then we're going
// assume it's a feed.
if ([urlString hasPrefix:@"feed:"])
return feedURLString;
if ([urlString hasSuffix:@".rss"] || [urlString hasSuffix:@".rdf"] || [urlString hasSuffix:@".xml"])
return feedURLString;
// OK. Now we're at the point where can't be reasonably sure that
// the URL points to a feed. Time to look at the content.
NSURL * url = [NSURL URLWithString:urlString];
if ([url scheme] == nil)
{
urlString = [@"http://" stringByAppendingString:urlString];
url = [NSURL URLWithString:urlString];
}
// Use this rather than [NSData dataWithContentsOfURL:],
// because that method will not necessarily unzip gzipped content from server.
// Thanks to http://www.omnigroup.com/mailman/archive/macosx-dev/2004-March/051547.html
NSData * urlContent = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:NULL error:NULL];
if (urlContent == nil)
return feedURLString;
// Get all the feeds on the page. If there's more than one, use the first one. Later we
// could put up UI inviting the user to pick one but I don't know if it makes sense to
// do this. How would they know which one would be best? We'd have to query each feed, get
// the title and then ask them.
NSMutableArray * linkArray = [NSMutableArray arrayWithCapacity:10];
if ([RichXMLParser extractFeeds:urlContent toArray:linkArray])
{
NSString * feedPart = [linkArray objectAtIndex:0];
if (![feedPart hasPrefix:@"http:"])
{
if (![urlString hasSuffix:@"/"])
urlString = [urlString stringByAppendingString:@"/"];
feedURLString = [urlString stringByAppendingString:feedPart];
}
else
feedURLString = feedPart;
}
return feedURLString;
}
/* dealloc
* Clean up and release resources.
*/
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[sourcesDict release];
[db release];
[super dealloc];
}
@end