| 1 | /* Copyright (C) 2008-2009 Peter Speck |
|---|
| 2 | * |
|---|
| 3 | * This program is free software: you can redistribute it and/or modify |
|---|
| 4 | * it under the terms of the GNU General Public License as published by |
|---|
| 5 | * the Free Software Foundation, either version 3 of the License, or |
|---|
| 6 | * (at your option) any later version. |
|---|
| 7 | * |
|---|
| 8 | * This program is distributed in the hope that it will be useful, |
|---|
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 11 | * GNU General Public License for more details. |
|---|
| 12 | * |
|---|
| 13 | * You should have received a copy of the GNU General Public License |
|---|
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 15 | */ |
|---|
| 16 | |
|---|
| 17 | #import "Filter.h" |
|---|
| 18 | #import "GBDebug.h" |
|---|
| 19 | #import "AlertHelper.h" |
|---|
| 20 | #import "Prefs.h" |
|---|
| 21 | #import "XmlDoc.h" |
|---|
| 22 | #import "Xml.h" |
|---|
| 23 | #import "ServerCommunication.h" |
|---|
| 24 | #import "FilterTabController.h" |
|---|
| 25 | #import "FilterPublisher.h" |
|---|
| 26 | #import "FilterListView.h" |
|---|
| 27 | |
|---|
| 28 | NSString* updatePolicies[] = { |
|---|
| 29 | @"auto", @"notify", @"manual", NULL |
|---|
| 30 | }; |
|---|
| 31 | |
|---|
| 32 | NSString* safetyPolicies[] = { |
|---|
| 33 | @"simple", @"modification", NULL |
|---|
| 34 | }; |
|---|
| 35 | |
|---|
| 36 | //============================================================================================== |
|---|
| 37 | |
|---|
| 38 | @interface LabelTextField : NSTextField |
|---|
| 39 | { |
|---|
| 40 | Filter* _filter; |
|---|
| 41 | } |
|---|
| 42 | |
|---|
| 43 | - (id)initWithFrame:(NSRect)frameRect |
|---|
| 44 | withFilter:(Filter*)filter; |
|---|
| 45 | - (void)dealloc; |
|---|
| 46 | |
|---|
| 47 | @end |
|---|
| 48 | |
|---|
| 49 | @implementation LabelTextField |
|---|
| 50 | |
|---|
| 51 | - (id)initWithFrame:(NSRect)frameRect |
|---|
| 52 | withFilter:(Filter*)filter |
|---|
| 53 | { |
|---|
| 54 | if (!(self = [super initWithFrame:frameRect])) |
|---|
| 55 | return nil; |
|---|
| 56 | _filter = filter; // not retained, as it would create a ref-counter loop. |
|---|
| 57 | return self; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | - (void)dealloc // clang-sa wants a dealloc even though there's nothing to do. |
|---|
| 61 | { |
|---|
| 62 | [super dealloc]; |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | - (void)textDidEndEditing:(NSNotification *)aNotification |
|---|
| 66 | { |
|---|
| 67 | NSString*s = [self stringValue]; |
|---|
| 68 | [_filter endEditing]; |
|---|
| 69 | if ([s length]) { |
|---|
| 70 | [_filter setName:s]; |
|---|
| 71 | [[_filter comm] saveDirtyPrefsAndNotify]; |
|---|
| 72 | } |
|---|
| 73 | [_filter makeTableFirstResponder]; |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | @end |
|---|
| 77 | |
|---|
| 78 | //============================================================================================== |
|---|
| 79 | |
|---|
| 80 | @interface Filter() |
|---|
| 81 | |
|---|
| 82 | - (NSString*) getPathArgs:(NSString*)cmd; |
|---|
| 83 | - (void)updateViews; |
|---|
| 84 | |
|---|
| 85 | - (void)releaseUpdateWithCancel:(BOOL)cancel; |
|---|
| 86 | - (void)makeGlobeImageView:(NSImage*)image; |
|---|
| 87 | |
|---|
| 88 | - (void)enabledAction:(id)sender; |
|---|
| 89 | - (void)clickedGlobe:(id)sender; |
|---|
| 90 | |
|---|
| 91 | - (void)updateEditingSubscriptionDates; |
|---|
| 92 | -(void)startEditingSubscriptionOneShot:(id)sender; |
|---|
| 93 | @end |
|---|
| 94 | |
|---|
| 95 | @implementation Filter |
|---|
| 96 | |
|---|
| 97 | - (id)initWithFilterElement:(NSXMLElement*)filterE |
|---|
| 98 | withComm:(ServerCommunication*)comm |
|---|
| 99 | withTabController:(FilterTabController*)tabController |
|---|
| 100 | withFilterListView:(FilterListView*)filterListView |
|---|
| 101 | atIndex:(NSUInteger)idx |
|---|
| 102 | { |
|---|
| 103 | if (!(self = [super initWithFrame:NSMakeRect(0, 0, 265, 19)])) |
|---|
| 104 | return nil; |
|---|
| 105 | _comm = [comm retain]; |
|---|
| 106 | _prefs = [[comm prefs] retain]; |
|---|
| 107 | _alertHelper = [[_prefs alertHelper] retain]; |
|---|
| 108 | _filterE = [filterE retain]; |
|---|
| 109 | _filterId = [[Xml getAttribute:_filterE withName:@"id"] intValue]; |
|---|
| 110 | _url = [[Xml getAttribute:_filterE withName:@"url"] retain]; |
|---|
| 111 | _filterTabController = tabController; // not retained as it always lives longer. |
|---|
| 112 | if ([[filterE elementsForName:@"filter-data"] count]) { |
|---|
| 113 | _isSubscription = NO; |
|---|
| 114 | NSArray* a = [_filterE elementsForName:@"filter-data"]; |
|---|
| 115 | if ([a count] != 1) { |
|---|
| 116 | NSLog(@"Invalid <filter> in panel settings: %@", filterE); |
|---|
| 117 | [self release]; |
|---|
| 118 | return nil; |
|---|
| 119 | } |
|---|
| 120 | _filterDataE = [[a objectAtIndex:0] retain]; |
|---|
| 121 | } else { |
|---|
| 122 | _isSubscription = YES; |
|---|
| 123 | NSString* pathArgs = [self getPathArgs:@"/filters/subscription/get"]; |
|---|
| 124 | DebugNSLog(@"Getting sub info: %@", pathArgs); |
|---|
| 125 | XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL]; |
|---|
| 126 | if (!doc) { |
|---|
| 127 | _filterContainerE = [[Xml createElementWithName:@"filter-container"] retain]; |
|---|
| 128 | _filterDataE = [[Xml createElementWithName:@"filter-data"] retain]; |
|---|
| 129 | [Xml setAttribute:@"(Subscription not downloaded)" withName:@"name" inElement:_filterDataE]; |
|---|
| 130 | _subscriptionIsMissingDownload = YES; |
|---|
| 131 | } else { |
|---|
| 132 | _filterContainerE = [[doc byXPath:@"filter-container"] retain]; |
|---|
| 133 | _filterDataE = [[doc byXPath:@"filter-container/filter-data"] retain]; |
|---|
| 134 | if (!_filterDataE) { |
|---|
| 135 | NSLog(@"Invalid subscription xml: missing filter-data root child."); |
|---|
| 136 | [self release]; |
|---|
| 137 | return nil; |
|---|
| 138 | } |
|---|
| 139 | } |
|---|
| 140 | _subscriptionRuleStates = [[NSMutableDictionary dictionaryWithCapacity:10] retain]; |
|---|
| 141 | } |
|---|
| 142 | [self readAttributesFromXml]; |
|---|
| 143 | if (_isSubscription) { |
|---|
| 144 | _subscriptionDataFilePath = [[NSString stringWithFormat:@"/Library/GlimmerBlocker/Filter subscriptions/%d.xml", _filterId] retain]; |
|---|
| 145 | NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:_subscriptionDataFilePath error:NULL]; |
|---|
| 146 | _subscriptionDataFileModdate = [[attrs objectForKey:NSFileModificationDate] timeIntervalSince1970]; |
|---|
| 147 | } |
|---|
| 148 | _filterListView = [filterListView retain]; |
|---|
| 149 | [_filterListView addRow:self atIndex:idx]; |
|---|
| 150 | // |
|---|
| 151 | _checkbox = [[NSButton alloc] initWithFrame:NSMakeRect(3, 1, 16, 16)]; // size by mag.glass |
|---|
| 152 | [_checkbox setButtonType:NSSwitchButton]; |
|---|
| 153 | [_checkbox setTitle:@""]; |
|---|
| 154 | [_checkbox setTarget:self]; |
|---|
| 155 | [_checkbox setAction:@selector(enabledAction:)]; |
|---|
| 156 | [self addSubview:_checkbox]; |
|---|
| 157 | // |
|---|
| 158 | if (![self isSubscription]) { |
|---|
| 159 | [self updateViews]; |
|---|
| 160 | return self; |
|---|
| 161 | } |
|---|
| 162 | // |
|---|
| 163 | _warningButton = [[NSButton alloc] initWithFrame:NSMakeRect(226, 2, 15, 15)]; |
|---|
| 164 | [_warningButton setImagePosition:NSImageOnly]; |
|---|
| 165 | [_warningButton setButtonType:NSMomentaryLightButton]; |
|---|
| 166 | [_warningButton setBordered:NO]; |
|---|
| 167 | [_warningButton setImage:[_filterTabController warningImage]]; |
|---|
| 168 | [self addSubview:_warningButton]; |
|---|
| 169 | [_warningButton setHidden:!_updateError]; |
|---|
| 170 | [_warningButton setTarget:self]; |
|---|
| 171 | [_warningButton setAction:@selector(startEditingSubscriptionOneShot:)]; |
|---|
| 172 | [_warningButton sendActionOn:NSLeftMouseDownMask]; |
|---|
| 173 | // |
|---|
| 174 | _spinner = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(225, 1, 17, 17)]; |
|---|
| 175 | [_spinner setStyle:NSProgressIndicatorSpinningStyle]; |
|---|
| 176 | [_spinner setControlSize:NSSmallControlSize]; |
|---|
| 177 | [_spinner setDisplayedWhenStopped:NO]; |
|---|
| 178 | [_spinner sizeToFit]; |
|---|
| 179 | //DebugNSLog(@"spinner size: %@", NSStringFromSize([_spinner frame].size)); |
|---|
| 180 | [self addSubview:_spinner]; |
|---|
| 181 | // if (idx == 1) |
|---|
| 182 | // [self performSelector:@selector(startSpinner) withObject:NULL afterDelay:0.01]; |
|---|
| 183 | // don't know why it must be delayed. |
|---|
| 184 | [self updateViews]; |
|---|
| 185 | return self; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | - (void)startSpinner |
|---|
| 189 | { |
|---|
| 190 | [_spinner startAnimation:self]; |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | - (void)dealloc |
|---|
| 194 | { |
|---|
| 195 | DebugNSLog(@"Deallocs filter: %@", _name); |
|---|
| 196 | [self releaseUpdateWithCancel:YES]; |
|---|
| 197 | [_filterListView removeRow:self]; |
|---|
| 198 | [_alertHelper release]; |
|---|
| 199 | [_prefs release]; |
|---|
| 200 | [_name release]; |
|---|
| 201 | [_comments release]; |
|---|
| 202 | [_description release]; |
|---|
| 203 | [_filterE release]; |
|---|
| 204 | [_filterContainerE release]; |
|---|
| 205 | [_filterDataE release]; |
|---|
| 206 | [_url release]; |
|---|
| 207 | [_updateError release]; |
|---|
| 208 | [_subscriptionRuleStates release]; |
|---|
| 209 | [_subscriptionDataFilePath release]; |
|---|
| 210 | _filterTabController = NULL; // not retained |
|---|
| 211 | // |
|---|
| 212 | [_spinner release]; |
|---|
| 213 | [_globeButton release]; |
|---|
| 214 | [_warningButton release]; |
|---|
| 215 | [_label release]; |
|---|
| 216 | [_checkbox release]; |
|---|
| 217 | [_filterListView release]; |
|---|
| 218 | // |
|---|
| 219 | [editSheet release]; |
|---|
| 220 | [super dealloc]; |
|---|
| 221 | } |
|---|
| 222 | |
|---|
| 223 | - (void)makeGlobeImageView:(NSImage*)image |
|---|
| 224 | { |
|---|
| 225 | if ([_globeButton image] == image) |
|---|
| 226 | return; |
|---|
| 227 | if (!_globeButton) { |
|---|
| 228 | _globeButton = [[NSButton alloc] initWithFrame:NSMakeRect(245, 1, 17, 17)]; |
|---|
| 229 | [_globeButton setImagePosition:NSImageOnly]; |
|---|
| 230 | [_globeButton setButtonType:NSMomentaryLightButton]; |
|---|
| 231 | [_globeButton setBordered:NO]; |
|---|
| 232 | [self addSubview:_globeButton]; |
|---|
| 233 | [_globeButton setTarget:self]; |
|---|
| 234 | [_globeButton setAction:@selector(clickedGlobe:)]; |
|---|
| 235 | [_globeButton sendActionOn:NSLeftMouseDownMask]; |
|---|
| 236 | } |
|---|
| 237 | [_globeButton setImage:image]; |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | - (NSImage*)getInfoImage |
|---|
| 241 | { |
|---|
| 242 | if (_updateUrl) |
|---|
| 243 | return NULL; |
|---|
| 244 | if (_updateError) |
|---|
| 245 | return [_filterTabController warningImage]; |
|---|
| 246 | if (_filterContainerE && [[Xml nodesForXPath:@"available-update-last-modified" withRoot:_filterContainerE] count]) |
|---|
| 247 | return [_filterTabController updateAvailableImage]; |
|---|
| 248 | return NULL; |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | - (void)updateViews |
|---|
| 252 | { |
|---|
| 253 | [_checkbox setEnabled:[_prefs hasGlimmerAuth]]; |
|---|
| 254 | [_checkbox setState:[self enabled]]; |
|---|
| 255 | NSImage* infoImage = [self getInfoImage]; |
|---|
| 256 | [_warningButton setHidden:!infoImage]; |
|---|
| 257 | if (infoImage) |
|---|
| 258 | [_warningButton setImage:infoImage]; |
|---|
| 259 | if (_updateUrl) |
|---|
| 260 | [_spinner startAnimation:self]; |
|---|
| 261 | else |
|---|
| 262 | [_spinner stopAnimation:self]; |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | - (void)decodeSubscriptionRuleStates:(NSString*)encoded withState:(NSNumber*)state |
|---|
| 266 | { |
|---|
| 267 | if (![encoded length]) |
|---|
| 268 | return; |
|---|
| 269 | for (NSString* s in [encoded componentsSeparatedByString:@";"]) |
|---|
| 270 | [_subscriptionRuleStates setObject:state forKey:s]; |
|---|
| 271 | } |
|---|
| 272 | |
|---|
| 273 | - (void)readSubscriptionRuleStates |
|---|
| 274 | { |
|---|
| 275 | if (![self isSubscription]) |
|---|
| 276 | return; |
|---|
| 277 | [_subscriptionRuleStates removeAllObjects]; |
|---|
| 278 | [self decodeSubscriptionRuleStates:[Xml getAttribute:_filterE withName:@"rules-enabled"] |
|---|
| 279 | withState:[NSNumber numberWithInt:NSOnState]]; |
|---|
| 280 | [self decodeSubscriptionRuleStates:[Xml getAttribute:_filterE withName:@"rules-disabled"] |
|---|
| 281 | withState:[NSNumber numberWithInt:NSOffState]]; |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | - (void)setUpdateError:(NSString*)s |
|---|
| 285 | { |
|---|
| 286 | DebugNSLog(@"setUpdateError(%@): %@", _name, s); |
|---|
| 287 | [_updateError release]; |
|---|
| 288 | _updateError = [s retain]; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | - (void)readAttributesFromXml |
|---|
| 292 | { |
|---|
| 293 | _enabled = [[Xml getAttribute:_filterE withName:@"enabled"] intValue]; |
|---|
| 294 | // |
|---|
| 295 | [_name release]; |
|---|
| 296 | _name = [[Xml getAttribute:_filterDataE withName:@"name"] retain]; |
|---|
| 297 | // |
|---|
| 298 | [_description release]; |
|---|
| 299 | _description = [Xml stringForXPath:@"description" withRoot:_filterDataE]; |
|---|
| 300 | if (![_description length]) |
|---|
| 301 | _description = [Xml getAttribute:_filterDataE withName:@"description"]; // old format stores comments in attribute. |
|---|
| 302 | [_description retain]; |
|---|
| 303 | // |
|---|
| 304 | [_comments release]; |
|---|
| 305 | _comments = [Xml stringForXPath:@"comments" withRoot:_filterE]; |
|---|
| 306 | if (![_comments length]) |
|---|
| 307 | _comments = [Xml getAttribute:_filterE withName:@"comments"]; // old format stores comments in attribute. |
|---|
| 308 | [_comments retain]; |
|---|
| 309 | // |
|---|
| 310 | _safetySimpleRulesOnly = _isSubscription && ![[Xml getAttribute:_filterE withName:@"safety-policy"] isEqual:@"modification"]; |
|---|
| 311 | // |
|---|
| 312 | [self readSubscriptionRuleStates]; |
|---|
| 313 | if ([self isSubscription]) { |
|---|
| 314 | [self makeGlobeImageView:[_filterTabController subscriptionGlobeImage]]; |
|---|
| 315 | NSString* tip = [NSString stringWithFormat:@"Subscription:\n%@", [self url]]; |
|---|
| 316 | NSString* s = [Xml dateFromMillisecsXPath:@"last-modified/@msecs" withRoot:_filterContainerE]; |
|---|
| 317 | if (s) |
|---|
| 318 | tip = [NSString stringWithFormat:@"%@\n\nLast modification by author:\n %@", tip, s]; |
|---|
| 319 | s = [Xml dateFromMillisecsXPath:@"last-update-check/@msecs" withRoot:_filterContainerE]; |
|---|
| 320 | if (s) |
|---|
| 321 | tip = [NSString stringWithFormat:@"%@\nLast check for updates:\n %@", tip, s]; |
|---|
| 322 | tip = [tip stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"]; // tried to avoid line breaking... |
|---|
| 323 | [_globeButton setToolTip:tip]; |
|---|
| 324 | [self setUpdateError:[Xml getAttribute:_filterContainerE withName:@"network-error"]]; |
|---|
| 325 | } else if ([self url]) { |
|---|
| 326 | if ([self isDirty]) |
|---|
| 327 | [self makeGlobeImageView:[_filterTabController publishGlobeDirtyImage]]; |
|---|
| 328 | else |
|---|
| 329 | [self makeGlobeImageView:[_filterTabController publishGlobeImage]]; |
|---|
| 330 | NSString* tip = [NSString stringWithFormat:@"Published at:\n%@", [self url]]; |
|---|
| 331 | NSString* s = [Xml dateFromMillisecsXPath:@"last-publish-date/@msecs" withRoot:_filterE]; |
|---|
| 332 | if (s) |
|---|
| 333 | tip = [NSString stringWithFormat:@"%@\n\nTime of latest publication:\n %@", tip, s]; |
|---|
| 334 | s = [Xml dateFromMillisecsXPath:@"first-dirty-date/@msecs" withRoot:_filterE]; |
|---|
| 335 | if (s) |
|---|
| 336 | tip = [NSString stringWithFormat:@"%@\nHas unpublished changes since:\n %@", tip, s]; |
|---|
| 337 | tip = [tip stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"]; // tried to avoid line breaking... |
|---|
| 338 | [_globeButton setToolTip:tip]; |
|---|
| 339 | } else { |
|---|
| 340 | [_globeButton setToolTip:@""]; |
|---|
| 341 | } |
|---|
| 342 | // |
|---|
| 343 | [self updateViews]; |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | - (NSNumber*)getRuleSubscriptionState:(NSString*)ruleId |
|---|
| 347 | { |
|---|
| 348 | NSNumber* n = [_subscriptionRuleStates objectForKey:ruleId]; |
|---|
| 349 | return n ? n : [NSNumber numberWithInt:NSMixedState]; |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | - (void)setRuleSubscriptionState:(NSNumber*)state forRuleId:(NSString*)ruleId |
|---|
| 353 | { |
|---|
| 354 | if ([state intValue] == NSMixedState) |
|---|
| 355 | [_subscriptionRuleStates removeObjectForKey:ruleId]; |
|---|
| 356 | else |
|---|
| 357 | [_subscriptionRuleStates setObject:state forKey:ruleId]; |
|---|
| 358 | NSMutableString* enabled = [NSMutableString stringWithCapacity:100]; |
|---|
| 359 | NSMutableString* disabled = [NSMutableString stringWithCapacity:100]; |
|---|
| 360 | for (NSXMLElement* ruleE in [[self filterDataE] elementsForName:@"rule"]) { |
|---|
| 361 | ruleId = [Xml getAttribute:ruleE withName:@"rule-id"]; |
|---|
| 362 | if (!ruleId) |
|---|
| 363 | continue; |
|---|
| 364 | state = [_subscriptionRuleStates objectForKey:ruleId]; |
|---|
| 365 | if (!state) |
|---|
| 366 | continue; |
|---|
| 367 | NSMutableString* ms = ([state intValue] == NSOnState) ? enabled : disabled; |
|---|
| 368 | if ([ms length]) |
|---|
| 369 | [ms appendString:@";"]; |
|---|
| 370 | [ms appendString:ruleId]; |
|---|
| 371 | } |
|---|
| 372 | [Xml setAttribute:enabled withName:@"rules-enabled" inElement:_filterE]; |
|---|
| 373 | [Xml setAttribute:disabled withName:@"rules-disabled" inElement:_filterE]; |
|---|
| 374 | [_prefs markAsDirty]; |
|---|
| 375 | [_comm saveDirtyPrefsAndNotify]; |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | //--------------------------------------- |
|---|
| 379 | - (BOOL)deleteFilter |
|---|
| 380 | { |
|---|
| 381 | if (![self isSubscription]) { |
|---|
| 382 | [_filterE detach]; |
|---|
| 383 | [_filterListView removeRow:self]; |
|---|
| 384 | [_prefs markAsDirty]; |
|---|
| 385 | return YES; |
|---|
| 386 | } |
|---|
| 387 | if (![_comm isServerRunning]) { |
|---|
| 388 | [_alertHelper prepareWarningAlertWithTitle:@"Can't delete subscriptions when GlimmerBlocker is not activated" |
|---|
| 389 | withInformativeText:@"You must activate GlimmerBlocker before deleting the filter"]; |
|---|
| 390 | [_alertHelper showPreparedAlert]; |
|---|
| 391 | return NO; |
|---|
| 392 | } |
|---|
| 393 | NSString* pathArgs = [self getPathArgs:@"!/filters/subscription/remove"]; |
|---|
| 394 | XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:@"Failed deleting subscription"]; |
|---|
| 395 | if (!doc) |
|---|
| 396 | return NO; |
|---|
| 397 | [_filterE detach]; |
|---|
| 398 | [_filterListView removeRow:self]; |
|---|
| 399 | [_prefs markAsDirty]; |
|---|
| 400 | [_comm saveDirtyPrefsAndNotify]; |
|---|
| 401 | return YES; |
|---|
| 402 | } |
|---|
| 403 | |
|---|
| 404 | //--------------------------------------- |
|---|
| 405 | |
|---|
| 406 | - (Prefs*)prefs |
|---|
| 407 | { |
|---|
| 408 | return _prefs; |
|---|
| 409 | } |
|---|
| 410 | |
|---|
| 411 | - (ServerCommunication*)comm |
|---|
| 412 | { |
|---|
| 413 | return _comm; |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | - (NSXMLElement*)filterE |
|---|
| 417 | { |
|---|
| 418 | return _filterE; |
|---|
| 419 | } |
|---|
| 420 | |
|---|
| 421 | - (NSXMLElement*)filterDataE |
|---|
| 422 | { |
|---|
| 423 | return _filterDataE; |
|---|
| 424 | } |
|---|
| 425 | |
|---|
| 426 | - (NSXMLElement*)ruleWithId:(int)ruleId |
|---|
| 427 | { |
|---|
| 428 | if (!_filterDataE) |
|---|
| 429 | return NULL; |
|---|
| 430 | for (NSXMLElement* ruleE in [_filterDataE elementsForName:@"rule"]) { |
|---|
| 431 | if ([Xml getIntAttribute:ruleE withName:@"rule-id"] == ruleId) |
|---|
| 432 | return ruleE; |
|---|
| 433 | } |
|---|
| 434 | return NULL; |
|---|
| 435 | } |
|---|
| 436 | |
|---|
| 437 | - (BOOL)isDirty |
|---|
| 438 | { |
|---|
| 439 | if (![self url] || [self isSubscription]) |
|---|
| 440 | return NO; |
|---|
| 441 | return [[_filterE elementsForName:@"first-dirty-date"] count] > 0; |
|---|
| 442 | } |
|---|
| 443 | |
|---|
| 444 | - (void)notifyDirty |
|---|
| 445 | { |
|---|
| 446 | [_prefs markAsDirty]; |
|---|
| 447 | if (![self url] || [self isSubscription]) |
|---|
| 448 | return; |
|---|
| 449 | [Xml singletonDateElement:[NSDate date] forXPath:@"first-dirty-date" fromParent:_filterE updateExisting:NO]; |
|---|
| 450 | [self readAttributesFromXml]; |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | - (void)notifyPublished |
|---|
| 454 | { |
|---|
| 455 | NSAssert([self url] && ![self isSubscription], @"is-subscription"); |
|---|
| 456 | for (NSXMLElement* e in [_filterE elementsForName:@"first-dirty-date"]) |
|---|
| 457 | [e detach]; |
|---|
| 458 | [Xml singletonDateElement:[NSDate date] forXPath:@"last-publish-date" fromParent:_filterE updateExisting:YES]; |
|---|
| 459 | } |
|---|
| 460 | |
|---|
| 461 | - (void)notifyUpdatedAuthorizationState |
|---|
| 462 | { |
|---|
| 463 | [self updateViews]; |
|---|
| 464 | } |
|---|
| 465 | //--------------------------------------- |
|---|
| 466 | |
|---|
| 467 | - (BOOL)enabled |
|---|
| 468 | { |
|---|
| 469 | return _enabled; |
|---|
| 470 | } |
|---|
| 471 | |
|---|
| 472 | - (void)filterEnabled:(BOOL)enabled |
|---|
| 473 | { |
|---|
| 474 | if (enabled == _enabled) |
|---|
| 475 | return; |
|---|
| 476 | _enabled = enabled; |
|---|
| 477 | [Xml setAttribute:(enabled ? @"1" : @"") withName:@"enabled" inElement:_filterE]; |
|---|
| 478 | [_prefs markAsDirty]; |
|---|
| 479 | } |
|---|
| 480 | |
|---|
| 481 | - (void)enabledAction:(id)sender |
|---|
| 482 | { |
|---|
| 483 | [self filterEnabled:[sender state]]; |
|---|
| 484 | [_filterTabController updateTabViewWithForceUpdate:NO]; |
|---|
| 485 | [_comm saveDirtyPrefsAndNotify]; |
|---|
| 486 | if ([sender state]) { |
|---|
| 487 | // make the server check for an update if the filter hasn't been updated for a while. |
|---|
| 488 | NSURLResponse* response = NULL; |
|---|
| 489 | [_comm curlToData:@"!/filters/subscription/check-updates?softcheck=1" withResponse:&response withErrorRef:NULL]; |
|---|
| 490 | } |
|---|
| 491 | } |
|---|
| 492 | //--------------------------------------- |
|---|
| 493 | |
|---|
| 494 | - (int)filterId |
|---|
| 495 | { |
|---|
| 496 | return _filterId; |
|---|
| 497 | } |
|---|
| 498 | |
|---|
| 499 | //--------------------------------------- |
|---|
| 500 | |
|---|
| 501 | - (NSString*)name |
|---|
| 502 | { |
|---|
| 503 | return _name ? _name : @""; |
|---|
| 504 | } |
|---|
| 505 | |
|---|
| 506 | - (void)setName:(NSString*)s |
|---|
| 507 | { |
|---|
| 508 | NSAssert([self canEditName], @"setName without canEditName"); |
|---|
| 509 | if ([s isEqual:[self name]]) |
|---|
| 510 | return; |
|---|
| 511 | [_name release]; |
|---|
| 512 | _name = [s retain]; |
|---|
| 513 | [_label setStringValue:_name]; |
|---|
| 514 | [Xml setAttribute:_name withName:@"name" inElement:_filterDataE]; |
|---|
| 515 | [self notifyDirty]; |
|---|
| 516 | } |
|---|
| 517 | |
|---|
| 518 | //--------------------------------------- |
|---|
| 519 | |
|---|
| 520 | - (NSString*)description |
|---|
| 521 | { |
|---|
| 522 | return _description ? _description : @""; |
|---|
| 523 | } |
|---|
| 524 | |
|---|
| 525 | //--------------------------------------- |
|---|
| 526 | |
|---|
| 527 | - (NSString*)comments |
|---|
| 528 | { |
|---|
| 529 | return _comments ? _comments : @""; |
|---|
| 530 | } |
|---|
| 531 | |
|---|
| 532 | - (void)setComments:(NSString*)s |
|---|
| 533 | { |
|---|
| 534 | NSAssert([self canEditComments], @"setComments without canEditComments"); |
|---|
| 535 | if ([s isEqual:[self comments]]) |
|---|
| 536 | return; |
|---|
| 537 | [_comments release]; |
|---|
| 538 | _comments = [s retain]; |
|---|
| 539 | [Xml setCData:_comments forXPath:@"comments" withRoot:_filterE]; |
|---|
| 540 | [Xml setAttribute:NULL withName:@"comments" inElement:_filterE]; // storing in attribute is old format. |
|---|
| 541 | [self notifyDirty]; |
|---|
| 542 | } |
|---|
| 543 | |
|---|
| 544 | //--------------------------------------- |
|---|
| 545 | |
|---|
| 546 | - (BOOL)isSubscription |
|---|
| 547 | { |
|---|
| 548 | return _isSubscription; |
|---|
| 549 | } |
|---|
| 550 | |
|---|
| 551 | + (NSString*)extractHostFromStringUrl:(NSString*)url |
|---|
| 552 | { |
|---|
| 553 | NSString* host; |
|---|
| 554 | if ([url hasPrefix:@"http://"]) |
|---|
| 555 | host = [url substringFromIndex:7]; |
|---|
| 556 | else if ([url hasPrefix:@"https://"]) |
|---|
| 557 | host = [url substringFromIndex:8]; |
|---|
| 558 | else |
|---|
| 559 | return NULL; |
|---|
| 560 | NSInteger len = [host length]; |
|---|
| 561 | for (int i = 0; i < len; i++) { |
|---|
| 562 | unichar ch = [host characterAtIndex:i]; |
|---|
| 563 | if (ch == ':' || ch == '/') |
|---|
| 564 | return [host substringToIndex:i]; |
|---|
| 565 | } |
|---|
| 566 | return host; |
|---|
| 567 | } |
|---|
| 568 | |
|---|
| 569 | - (NSString*)publishHost |
|---|
| 570 | { |
|---|
| 571 | return _isSubscription ? NULL : [Filter extractHostFromStringUrl:_url]; |
|---|
| 572 | } |
|---|
| 573 | |
|---|
| 574 | - (BOOL)isPublished |
|---|
| 575 | { |
|---|
| 576 | return !_isSubscription && _url; |
|---|
| 577 | } |
|---|
| 578 | |
|---|
| 579 | - (BOOL)isMobileMePublished |
|---|
| 580 | { |
|---|
| 581 | NSString* host = [self publishHost]; |
|---|
| 582 | return [host isEqual:@"idisk.mac.com"] || [host isEqual:@"idisk.me.com"]; |
|---|
| 583 | } |
|---|
| 584 | |
|---|
| 585 | - (BOOL)isWebDavPublished |
|---|
| 586 | { |
|---|
| 587 | return [self publishHost] && ![self isMobileMePublished]; |
|---|
| 588 | } |
|---|
| 589 | |
|---|
| 590 | -(BOOL)safetySimpleRulesOnly |
|---|
| 591 | { |
|---|
| 592 | return _safetySimpleRulesOnly; |
|---|
| 593 | } |
|---|
| 594 | |
|---|
| 595 | - (NSString*)url |
|---|
| 596 | { |
|---|
| 597 | return _url; |
|---|
| 598 | } |
|---|
| 599 | |
|---|
| 600 | - (void)setUrl:(NSString*)url |
|---|
| 601 | { |
|---|
| 602 | [_url release]; |
|---|
| 603 | _url = [url retain]; |
|---|
| 604 | [Xml setAttribute:url withName:@"url" inElement:_filterE]; |
|---|
| 605 | [self notifyDirty]; |
|---|
| 606 | [self setNeedsDisplay:YES]; |
|---|
| 607 | } |
|---|
| 608 | |
|---|
| 609 | - (BOOL)canEditName |
|---|
| 610 | { |
|---|
| 611 | return ![self isSubscription]; |
|---|
| 612 | } |
|---|
| 613 | |
|---|
| 614 | - (BOOL)canEditComments |
|---|
| 615 | { |
|---|
| 616 | return ![self isSubscription]; |
|---|
| 617 | } |
|---|
| 618 | |
|---|
| 619 | - (BOOL)canEditRules |
|---|
| 620 | { |
|---|
| 621 | return ![self isSubscription]; |
|---|
| 622 | } |
|---|
| 623 | |
|---|
| 624 | - (BOOL)canDelete |
|---|
| 625 | { |
|---|
| 626 | NSString* url = [Xml getAttribute:_filterE withName:@"url"]; |
|---|
| 627 | return !url || ![url hasPrefix:@"http://glimmerblocker.org/site/filters/default/"]; |
|---|
| 628 | } |
|---|
| 629 | |
|---|
| 630 | - (void)startEditingPart2 |
|---|
| 631 | { |
|---|
| 632 | [[self window] makeFirstResponder:_label]; |
|---|
| 633 | } |
|---|
| 634 | |
|---|
| 635 | - (void)startEditingName |
|---|
| 636 | { |
|---|
| 637 | if (_label) |
|---|
| 638 | return; |
|---|
| 639 | [_filterListView setSelectionByView:self]; |
|---|
| 640 | // must be 1 pixel to the left of the drawString. |
|---|
| 641 | _label = [[LabelTextField alloc] initWithFrame:NSMakeRect(25, 2, 200, 17) withFilter:self]; |
|---|
| 642 | [_label setEditable:YES]; |
|---|
| 643 | [_label setSelectable:YES]; |
|---|
| 644 | [_label setDrawsBackground:YES]; |
|---|
| 645 | [_label setBezeled:NO]; |
|---|
| 646 | [_label setBordered:YES]; // If the border is omitted, the focus frame is not drawn???? |
|---|
| 647 | [_label setAlignment:NSLeftTextAlignment]; |
|---|
| 648 | [_label setStringValue:[self name]]; |
|---|
| 649 | [_label setDelegate:self]; |
|---|
| 650 | [self addSubview:_label]; |
|---|
| 651 | [self performSelector:@selector(startEditingPart2) withObject:NULL afterDelay:0.1]; |
|---|
| 652 | } |
|---|
| 653 | |
|---|
| 654 | - (BOOL)isEditing |
|---|
| 655 | { |
|---|
| 656 | return !!_label; |
|---|
| 657 | } |
|---|
| 658 | |
|---|
| 659 | - (void)endEditing |
|---|
| 660 | { |
|---|
| 661 | if (!_label) |
|---|
| 662 | return; |
|---|
| 663 | DebugNSLog(@"Filter endEditing"); |
|---|
| 664 | [_label removeFromSuperview]; |
|---|
| 665 | [_label release]; |
|---|
| 666 | _label = NULL; |
|---|
| 667 | } |
|---|
| 668 | |
|---|
| 669 | - (BOOL)control:(NSControl*)control |
|---|
| 670 | textView:(NSTextView*)fieldEditor doCommandBySelector:(SEL)commandSelector |
|---|
| 671 | { |
|---|
| 672 | if (_label && commandSelector == @selector(insertNewline:)) { |
|---|
| 673 | [self endEditing]; |
|---|
| 674 | return YES; |
|---|
| 675 | } |
|---|
| 676 | return NO; |
|---|
| 677 | } |
|---|
| 678 | |
|---|
| 679 | - (void)drawLabelUsingFocusColor:(BOOL)usingFocusColor |
|---|
| 680 | { |
|---|
| 681 | NSMutableDictionary* attrs = [NSMutableDictionary dictionaryWithCapacity:3]; |
|---|
| 682 | [attrs setObject:[NSFont systemFontOfSize:[NSFont systemFontSize]] forKey:NSFontAttributeName]; |
|---|
| 683 | NSColor* color; |
|---|
| 684 | if (usingFocusColor) |
|---|
| 685 | color = [NSColor alternateSelectedControlTextColor]; |
|---|
| 686 | else if (_subscriptionIsMissingDownload) |
|---|
| 687 | color = [NSColor redColor]; |
|---|
| 688 | else |
|---|
| 689 | color = [NSColor blackColor]; |
|---|
| 690 | [attrs setObject:color forKey:NSForegroundColorAttributeName]; |
|---|
| 691 | NSRect labelRect = NSMakeRect(26, 0, 200, 19); |
|---|
| 692 | NSString* s = [self name]; |
|---|
| 693 | [s drawInRect:labelRect withAttributes:attrs]; |
|---|
| 694 | } |
|---|
| 695 | |
|---|
| 696 | - (void)drawRect:(NSRect)r |
|---|
| 697 | { |
|---|
| 698 | [super drawRect:r]; |
|---|
| 699 | if (!_label) |
|---|
| 700 | [self drawLabelUsingFocusColor:[self shouldDrawUsingFocusColor]]; |
|---|
| 701 | } |
|---|
| 702 | |
|---|
| 703 | - (void)mouseUp:(NSEvent *)theEvent |
|---|
| 704 | { |
|---|
| 705 | NSTimeInterval dur = [theEvent timestamp] - _lastMouseDownTime; |
|---|
| 706 | if (dur > 1) // any NSxxx ? |
|---|
| 707 | return; |
|---|
| 708 | if ([self isSubscription]) { |
|---|
| 709 | if (_mouseUpIsDoubleClick) |
|---|
| 710 | [self startEditingSubscription:NO]; |
|---|
| 711 | return; |
|---|
| 712 | } |
|---|
| 713 | if (_mouseUpCanStartEditing && [self canEditName] && [_prefs hasGlimmerAuth]) |
|---|
| 714 | [self startEditingName]; |
|---|
| 715 | } |
|---|
| 716 | |
|---|
| 717 | + (NSString*)draggingDataType |
|---|
| 718 | { |
|---|
| 719 | return @"dk.vitality.glimmerblocker.filter"; |
|---|
| 720 | } |
|---|
| 721 | |
|---|
| 722 | - (void)mouseDown:(NSEvent *)theEvent |
|---|
| 723 | { |
|---|
| 724 | //DebugNSLog(@"mouseDown: %@", theEvent); |
|---|
| 725 | _lastMouseDownTime = [theEvent timestamp]; |
|---|
| 726 | BOOL tableWasFirstResponder = [_filterListView isFirstResponder]; |
|---|
| 727 | [[self window] makeFirstResponder:_filterListView]; |
|---|
| 728 | BOOL wasSelected = [self selected]; |
|---|
| 729 | [_filterListView setSelectionByView:self]; |
|---|
| 730 | _mouseClickLocation = [self convertPointFromBase:[theEvent locationInWindow]]; |
|---|
| 731 | NSUInteger cc = [theEvent clickCount]; |
|---|
| 732 | _mouseUpCanStartEditing = (cc == 2 || (cc == 1 && wasSelected && tableWasFirstResponder)); |
|---|
| 733 | _mouseUpIsDoubleClick = (cc == 2); |
|---|
| 734 | } |
|---|
| 735 | |
|---|
| 736 | - (void)mouseDragged:(NSEvent *)theEvent |
|---|
| 737 | { |
|---|
| 738 | //DebugNSLog(@"Mouse dragged: %@", theEvent); |
|---|
| 739 | NSPoint pos = [self convertPointFromBase:[theEvent locationInWindow]]; |
|---|
| 740 | if (ABS(pos.x - _mouseClickLocation.x) >= 10 || ABS(pos.y - _mouseClickLocation.y) < 5) |
|---|
| 741 | return; |
|---|
| 742 | DebugNSLog(@"Starts drag"); |
|---|
| 743 | // Make image snapshot |
|---|
| 744 | NSSize imgSize = [self bounds].size; |
|---|
| 745 | NSImage* img2 = [[[NSImage alloc] initWithSize:imgSize] autorelease]; |
|---|
| 746 | [img2 lockFocus]; |
|---|
| 747 | [self drawLabelUsingFocusColor:NO]; |
|---|
| 748 | [img2 unlockFocus]; |
|---|
| 749 | // Lighten it up, so it seems transparent |
|---|
| 750 | NSImage* image = [[[NSImage alloc] initWithSize:imgSize] autorelease]; |
|---|
| 751 | [image lockFocus]; |
|---|
| 752 | [img2 compositeToPoint:NSMakePoint(0, 0) operation:NSCompositePlusLighter fraction:0.5f]; |
|---|
| 753 | [image unlockFocus]; |
|---|
| 754 | // Start the dragging operation |
|---|
| 755 | [_filterListView startDrag:self withImage:image withEvent:theEvent]; |
|---|
| 756 | } |
|---|
| 757 | |
|---|
| 758 | - (void)makeTableFirstResponder |
|---|
| 759 | { |
|---|
| 760 | //DebugNSLog(@"makeTableFirstResponder"); |
|---|
| 761 | [[self window] makeFirstResponder:_filterListView]; |
|---|
| 762 | } |
|---|
| 763 | |
|---|
| 764 | - (void)releaseUpdateWithCancel:(BOOL)cancel |
|---|
| 765 | { |
|---|
| 766 | if (!_updateUrl) |
|---|
| 767 | return; |
|---|
| 768 | if (cancel) |
|---|
| 769 | [_updateConnection cancel]; |
|---|
| 770 | [_updateConnection release]; |
|---|
| 771 | _updateConnection = NULL; |
|---|
| 772 | [_updateData release]; |
|---|
| 773 | _updateData = NULL; |
|---|
| 774 | [_updateUrl release]; |
|---|
| 775 | _updateUrl = NULL; |
|---|
| 776 | } |
|---|
| 777 | |
|---|
| 778 | - (NSString*) getPathArgs:(NSString*)cmd |
|---|
| 779 | { |
|---|
| 780 | NSString* url = [Xml getAttribute:_filterE withName:@"url"]; |
|---|
| 781 | NSAssert(url, @"Must be a subscription"); |
|---|
| 782 | return [NSString stringWithFormat:@"%@?filter-id=%d", cmd, _filterId]; |
|---|
| 783 | } |
|---|
| 784 | |
|---|
| 785 | - (void)startUpdateSubscription |
|---|
| 786 | { |
|---|
| 787 | if (_updateUrl) { |
|---|
| 788 | NSLog(@"Ignores update-subscription request as already updating it: %@", _url); |
|---|
| 789 | return; |
|---|
| 790 | } |
|---|
| 791 | NSLog(@"Update subscription: %@", _url); |
|---|
| 792 | NSAssert([self isSubscription], @"Not a subscription"); |
|---|
| 793 | NSString* pathArgs = [self getPathArgs:@"!/filters/subscription/update"]; |
|---|
| 794 | _updateUrl = [[NSURL URLWithString:[_comm getAdminUrl:pathArgs]] retain]; |
|---|
| 795 | NSURLRequest* req = [NSURLRequest requestWithURL:_updateUrl |
|---|
| 796 | cachePolicy:NSURLRequestReloadIgnoringLocalCacheData // want fresh data |
|---|
| 797 | timeoutInterval:60.0]; |
|---|
| 798 | _updateData = [[NSMutableData data] retain]; |
|---|
| 799 | _updateConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self]; |
|---|
| 800 | if (!_updateConnection) { |
|---|
| 801 | [self releaseUpdateWithCancel:NO]; |
|---|
| 802 | [self setUpdateError:@"Communication with proxy failed."]; |
|---|
| 803 | } |
|---|
| 804 | [self updateViews]; |
|---|
| 805 | } |
|---|
| 806 | |
|---|
| 807 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response |
|---|
| 808 | { |
|---|
| 809 | // called for each response, i.e. for each redirect |
|---|
| 810 | DebugNSLog(@"FilterSub.didReceiveResponse: %@", response); |
|---|
| 811 | [_updateData setLength:0]; |
|---|
| 812 | } |
|---|
| 813 | |
|---|
| 814 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data |
|---|
| 815 | { |
|---|
| 816 | [_updateData appendData:data]; |
|---|
| 817 | } |
|---|
| 818 | |
|---|
| 819 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error |
|---|
| 820 | { |
|---|
| 821 | NSString* a = [error localizedDescription]; |
|---|
| 822 | NSString* b = [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]; |
|---|
| 823 | [self setUpdateError:[NSString stringWithFormat:@"%@ %@", a, b]]; |
|---|
| 824 | DebugNSLog(@"FilterSub.didFailWithError: %@", _updateError); |
|---|
| 825 | [self releaseUpdateWithCancel:NO]; |
|---|
| 826 | [self updateViews]; |
|---|
| 827 | } |
|---|
| 828 | |
|---|
| 829 | - (void)didGetNewSubscriptionData |
|---|
| 830 | { |
|---|
| 831 | _subscriptionIsMissingDownload = NO; |
|---|
| 832 | [self readAttributesFromXml]; |
|---|
| 833 | [_filterTabController notifyUpdatedFilter:self]; |
|---|
| 834 | [self updateEditingSubscriptionDates]; |
|---|
| 835 | } |
|---|
| 836 | |
|---|
| 837 | - (void)decodeUpdateResponse |
|---|
| 838 | { |
|---|
| 839 | XmlDoc* doc = [[[XmlDoc alloc] initWithURL:_updateUrl withData:_updateData] autorelease]; |
|---|
| 840 | if (![doc rootE]) { |
|---|
| 841 | [self setUpdateError:@"Could not get proper xml response from server"]; |
|---|
| 842 | return; |
|---|
| 843 | } |
|---|
| 844 | [self setNeedsDisplay:YES]; |
|---|
| 845 | NSString* s = [Xml getAttribute:[doc rootE] withName:@"error"]; |
|---|
| 846 | if (s) { |
|---|
| 847 | [self setUpdateError:s]; |
|---|
| 848 | return; |
|---|
| 849 | } |
|---|
| 850 | NSXMLElement* filterContainerE = [doc byXPath:@"filter-container"]; |
|---|
| 851 | NSXMLElement* filterDataE = [doc byXPath:@"filter-container/filter-data"]; |
|---|
| 852 | if (!filterContainerE || !filterDataE) |
|---|
| 853 | return; // not updated. |
|---|
| 854 | [_filterDataE release]; |
|---|
| 855 | _filterDataE = [filterDataE retain]; |
|---|
| 856 | [_filterContainerE release]; |
|---|
| 857 | _filterContainerE = [filterContainerE retain]; |
|---|
| 858 | [_prefs markAsDirty]; |
|---|
| 859 | [self didGetNewSubscriptionData]; |
|---|
| 860 | } |
|---|
| 861 | |
|---|
| 862 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection |
|---|
| 863 | { |
|---|
| 864 | [self setUpdateError:NULL]; |
|---|
| 865 | [self decodeUpdateResponse]; |
|---|
| 866 | [self releaseUpdateWithCancel:NO]; |
|---|
| 867 | [self updateViews]; |
|---|
| 868 | } |
|---|
| 869 | |
|---|
| 870 | - (void)clickedGlobeAlertCallbackWithReturnCode:(NSInteger)returnCode |
|---|
| 871 | { |
|---|
| 872 | if (returnCode == NSAlertFirstButtonReturn) |
|---|
| 873 | [[[FilterPublisher alloc] initWithFilterTabController:_filterTabController |
|---|
| 874 | withFilter:self |
|---|
| 875 | usingMobileMe:[self isMobileMePublished]] autorelease]; |
|---|
| 876 | } |
|---|
| 877 | |
|---|
| 878 | // callback from ImageButton |
|---|
| 879 | - (void)clickedGlobe:(id)sender |
|---|
| 880 | { |
|---|
| 881 | [_filterListView setSelectionByView:self]; |
|---|
| 882 | if ([self isSubscription]) { |
|---|
| 883 | [self startEditingSubscription:NO]; |
|---|
| 884 | //[_alertHelper prepareInformativeAlertWithTitle:[self name] withInformativeText:_url]; |
|---|
| 885 | //[_alertHelper showPreparedAlert]; |
|---|
| 886 | return; |
|---|
| 887 | } |
|---|
| 888 | NSString* info = [self url]; |
|---|
| 889 | NSString* s = [Xml dateFromMillisecsXPath:@"last-publish-date/@msecs" withRoot:_filterE]; |
|---|
| 890 | if (s) |
|---|
| 891 | info = [NSString stringWithFormat:@"%@\n\nTime of latest publication:\n %@", info, s]; |
|---|
| 892 | NSString* dirty = [Xml dateFromMillisecsXPath:@"first-dirty-date/@msecs" withRoot:_filterE]; |
|---|
| 893 | if (dirty) |
|---|
| 894 | info = [NSString stringWithFormat:@"%@\n\nHas unpublished changes since:\n %@", info, dirty]; |
|---|
| 895 | [_alertHelper prepareInformativeAlertWithTitle:@"Published filter:" withInformativeText:info]; |
|---|
| 896 | // ensure some reasonable min-width of alert so URL isn't wrapped. |
|---|
| 897 | [_alertHelper setAccessoryView:[[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 425, 1)] autorelease]]; |
|---|
| 898 | NSButton* okBtn = [_alertHelper addButtonWithTitle:(dirty ? @"Publish changes" : @"Publish")]; |
|---|
| 899 | if (!dirty) |
|---|
| 900 | [okBtn setKeyEquivalent:@""]; |
|---|
| 901 | if (![_prefs hasGlimmerAuth]) |
|---|
| 902 | [okBtn setEnabled:NO]; |
|---|
| 903 | [_alertHelper addButtonWithTitle:@"Cancel"]; |
|---|
| 904 | [_alertHelper setDelegate:self withSelector:@selector(clickedGlobeAlertCallbackWithReturnCode:)]; |
|---|
| 905 | [_alertHelper showPreparedAlert]; |
|---|
| 906 | } |
|---|
| 907 | |
|---|
| 908 | - (void)checkReloadSubscriptionFilterData |
|---|
| 909 | { |
|---|
| 910 | if (!_isSubscription) |
|---|
| 911 | return; |
|---|
| 912 | NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:_subscriptionDataFilePath error:nil]; |
|---|
| 913 | NSTimeInterval moddate = [[attrs objectForKey:NSFileModificationDate] timeIntervalSince1970]; |
|---|
| 914 | if (moddate == _subscriptionDataFileModdate) |
|---|
| 915 | return; |
|---|
| 916 | _subscriptionDataFileModdate = moddate; |
|---|
| 917 | DebugNSLog(@"Subscription changed on disk (%d.xml): %@", _filterId, [self name]); |
|---|
| 918 | NSString* pathArgs = [self getPathArgs:@"/filters/subscription/get"]; |
|---|
| 919 | DebugNSLog(@"Getting sub info: %@", pathArgs); |
|---|
| 920 | XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL]; |
|---|
| 921 | if (!doc) |
|---|
| 922 | return; |
|---|
| 923 | NSXMLElement* filterContainerE = [doc byXPath:@"filter-container"]; |
|---|
| 924 | NSXMLElement* filterDataE = [doc byXPath:@"filter-container/filter-data"]; |
|---|
| 925 | if (!filterContainerE || !filterDataE) { |
|---|
| 926 | NSLog(@"Invalid subscription xml: missing filter-data root child."); |
|---|
| 927 | return; |
|---|
| 928 | } |
|---|
| 929 | [filterContainerE retain]; |
|---|
| 930 | [filterDataE retain]; |
|---|
| 931 | // |
|---|
| 932 | [_filterContainerE release]; |
|---|
| 933 | [_filterDataE release]; |
|---|
| 934 | _filterContainerE = filterContainerE; |
|---|
| 935 | _filterDataE = filterDataE; |
|---|
| 936 | [self didGetNewSubscriptionData]; |
|---|
| 937 | } |
|---|
| 938 | |
|---|
| 939 | |
|---|
| 940 | #pragma mark ------------------- Subscription edit |
|---|
| 941 | |
|---|
| 942 | - (void)checkSubscriptionUrlSyntax |
|---|
| 943 | { |
|---|
| 944 | NSString* s = [editUrlTextField stringValue]; |
|---|
| 945 | NSString* errorMsg = [Xml getHttpUrlSyntaxError:s]; |
|---|
| 946 | if (!errorMsg && ![s hasSuffix:@".xml"]) |
|---|
| 947 | errorMsg = @"URL must end with '.xml'"; |
|---|
| 948 | [editUrlErrorField setStringValue:(errorMsg ? errorMsg : @"")]; |
|---|
| 949 | [editOkBtn setEnabled:(!errorMsg && [_prefs hasGlimmerAuth])]; |
|---|
| 950 | } |
|---|
| 951 | |
|---|
| 952 | - (void)controlTextDidChange:(NSNotification *)aNotification |
|---|
| 953 | { |
|---|
| 954 | if (editUrlTextField == [aNotification object]) |
|---|
| 955 | [self checkSubscriptionUrlSyntax]; |
|---|
| 956 | } |
|---|
| 957 | |
|---|
| 958 | - (void)updateEditingSubscriptionDates |
|---|
| 959 | { |
|---|
| 960 | NSMutableString* sb = [NSMutableString stringWithCapacity:0]; |
|---|
| 961 | // |
|---|
| 962 | NSString* s = [Xml dateFromMillisecsXPath:@"last-modified/@msecs" withRoot:_filterContainerE]; |
|---|
| 963 | [sb appendString:(s ? s : @"")]; |
|---|
| 964 | // |
|---|
| 965 | [sb appendString:@"\n"]; |
|---|
| 966 | s = [Xml dateFromMillisecsXPath:@"last-update-check/@msecs" withRoot:_filterContainerE]; |
|---|
| 967 | [sb appendString:(s ? s : @"")]; |
|---|
| 968 | // |
|---|
| 969 | [sb appendString:@"\n"]; |
|---|
| 970 | s = [Xml dateFromMillisecsXPath:@"subscribed-date/@msecs" withRoot:_filterContainerE]; |
|---|
| 971 | [sb appendString:(s ? s : @"")]; |
|---|
| 972 | // |
|---|
| 973 | [editUpdateDatesTextField setStringValue:sb]; |
|---|
| 974 | // |
|---|
| 975 | s = [Xml getAttribute:_filterContainerE withName:@"network-error"]; |
|---|
| 976 | if (s) { |
|---|
| 977 | [editUpdateErrorTextField setTextColor:[editSafetyWarningTextField textColor]]; |
|---|
| 978 | s = [@"Last update error:\n" stringByAppendingString:s]; |
|---|
| 979 | } else { |
|---|
| 980 | s = [Xml dateFromMillisecsXPath:@"available-update-last-modified/@msecs" withRoot:_filterContainerE]; |
|---|
| 981 | if (s) { |
|---|
| 982 | [editUpdateErrorTextField setTextColor:[NSColor colorWithDeviceRed:0 green:0.5f blue:0 alpha:1]]; |
|---|
| 983 | s = [@"Available update:\n" stringByAppendingString:s]; |
|---|
| 984 | } |
|---|
| 985 | } |
|---|
| 986 | [editUpdateErrorTextField setStringValue:(s ? s : @"")]; |
|---|
| 987 | } |
|---|
| 988 | |
|---|
| 989 | - (void)startEditingSubscription:(BOOL)oneshot |
|---|
| 990 | { |
|---|
| 991 | if (!editSheet) { |
|---|
| 992 | NSAssert([NSBundle loadNibNamed: @"FilterSubscriptionEdit" owner: self], @"Can't load nib 'FilterSubscriptionEdit'"); |
|---|
| 993 | NSAssert(editSheet, @"editSheet is null"); |
|---|
| 994 | } |
|---|
| 995 | [editNameTextField setStringValue:[self name]]; |
|---|
| 996 | [editUrlTextField setStringValue:[self url]]; |
|---|
| 997 | [editUrlTextField setDelegate:self]; |
|---|
| 998 | [self updateEditingSubscriptionDates]; |
|---|
| 999 | [GBDebug setVertMatrix:editUpdatesMatrix |
|---|
| 1000 | values:updatePolicies |
|---|
| 1001 | selectedValue:[Xml getAttribute:_filterE withName:@"update-policy"] |
|---|
| 1002 | defaultValue:@"notify"]; |
|---|
| 1003 | [GBDebug setVertMatrix:editSafetyMatrix |
|---|
| 1004 | values:safetyPolicies |
|---|
| 1005 | selectedValue:[Xml getAttribute:_filterE withName:@"safety-policy"] |
|---|
| 1006 | defaultValue:@"simple"]; |
|---|
| 1007 | BOOL editable = [_prefs hasGlimmerAuth]; |
|---|
| 1008 | [editUrlTextField setEnabled:editable]; |
|---|
| 1009 | [editUpdatesMatrix setEnabled:editable]; |
|---|
| 1010 | [editSafetyMatrix setEnabled:editable]; |
|---|
| 1011 | [editUpdateNowBtn setEnabled:editable]; |
|---|
| 1012 | [editOkBtn setEnabled:editable]; |
|---|
| 1013 | [editUpdateNowBtn setState:oneshot]; |
|---|
| 1014 | [self checkSubscriptionUrlSyntax]; |
|---|
| 1015 | [NSApp beginSheet: editSheet |
|---|
| 1016 | modalForWindow: [self window] |
|---|
| 1017 | modalDelegate: nil |
|---|
| 1018 | didEndSelector: nil |
|---|
| 1019 | contextInfo: nil]; |
|---|
| 1020 | } |
|---|
| 1021 | |
|---|
| 1022 | -(void)startEditingSubscriptionOneShot:(id)sender |
|---|
| 1023 | { |
|---|
| 1024 | [_filterListView setSelectionByView:self]; |
|---|
| 1025 | [self startEditingSubscription:YES]; |
|---|
| 1026 | } |
|---|
| 1027 | |
|---|
| 1028 | - (IBAction)editCancelBtnAction:(id)sender |
|---|
| 1029 | { |
|---|
| 1030 | [NSApp endSheet:editSheet]; |
|---|
| 1031 | [editSheet orderOut:self]; |
|---|
| 1032 | } |
|---|
| 1033 | |
|---|
| 1034 | - (IBAction)editOkBtnAction:(id)sender |
|---|
| 1035 | { |
|---|
| 1036 | [self editCancelBtnAction:self]; |
|---|
| 1037 | [self setUrl:[editUrlTextField stringValue]]; |
|---|
| 1038 | [Xml setAttribute:updatePolicies[[editUpdatesMatrix selectedRow]] withName:@"update-policy" inElement:_filterE]; |
|---|
| 1039 | [Xml setAttribute:safetyPolicies[[editSafetyMatrix selectedRow]] withName:@"safety-policy" inElement:_filterE]; |
|---|
| 1040 | [_prefs markAsDirty]; |
|---|
| 1041 | [self readAttributesFromXml]; |
|---|
| 1042 | [_comm saveDirtyPrefsAndNotify]; |
|---|
| 1043 | [_filterTabController redrawRulesTable]; |
|---|
| 1044 | if ([editUpdateNowBtn state]) |
|---|
| 1045 | [self startUpdateSubscription]; |
|---|
| 1046 | } |
|---|
| 1047 | |
|---|
| 1048 | @end |
|---|
| 1049 | |
|---|
| 1050 | @implementation Filter(NSDraggingSource) |
|---|
| 1051 | |
|---|
| 1052 | - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal |
|---|
| 1053 | { |
|---|
| 1054 | NSLog(@"Filter.draggingSourceOperationMaskForLocal.%d", isLocal); |
|---|
| 1055 | return isLocal ? NSDragOperationNone : NSDragOperationNone; |
|---|
| 1056 | } |
|---|
| 1057 | |
|---|
| 1058 | - (BOOL)ignoreModifierKeysWhileDragging |
|---|
| 1059 | { |
|---|
| 1060 | return YES; |
|---|
| 1061 | } |
|---|
| 1062 | |
|---|
| 1063 | - (void)draggedImage:(NSImage *)anImage beganAt:(NSPoint)aPoint |
|---|
| 1064 | { |
|---|
| 1065 | NSLog(@"beganAt: %@", NSStringFromPoint(aPoint)); |
|---|
| 1066 | } |
|---|
| 1067 | |
|---|
| 1068 | @end |
|---|