1/****************************************************************************** 2 * Copyright (c) 2009-2012 Transmission authors and contributors 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 * DEALINGS IN THE SOFTWARE. 21 *****************************************************************************/ 22 23#include <libtransmission/transmission.h> 24#include <libtransmission/utils.h> //tr_addressIsIP() 25 26#import "TrackerCell.h" 27#import "TrackerNode.h" 28 29#define PADDING_HORIZONAL 3.0 30#define PADDING_STATUS_HORIZONAL 3.0 31#define ICON_SIZE 16.0 32#define PADDING_BETWEEN_ICON_AND_NAME 4.0 33#define PADDING_ABOVE_ICON 1.0 34#define PADDING_ABOVE_NAME 1.0 35#define PADDING_BETWEEN_LINES 1.0 36#define PADDING_BETWEEN_LINES_ON_SAME_LINE 4.0 37#define COUNT_WIDTH 40.0 38 39@interface TrackerCell (Private) 40 41- (NSImage *) favIcon; 42- (void) loadTrackerIcon: (NSString *) baseAddress; 43 44- (NSRect) imageRectForBounds: (NSRect) bounds; 45- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds; 46- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds; 47- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds; 48- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect 49 inBounds: (NSRect) bounds; 50 51- (NSAttributedString *) attributedName; 52- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString; 53- (NSAttributedString *) attributedCount: (NSInteger) count; 54 55@end 56 57@implementation TrackerCell 58 59//make the favicons accessible to all tracker cells 60NSCache * fTrackerIconCache; 61NSMutableSet * fTrackerIconLoading; 62 63+ (void) initialize 64{ 65 fTrackerIconCache = [[NSCache alloc] init]; 66 fTrackerIconLoading = [[NSMutableSet alloc] init]; 67} 68 69- (id) init 70{ 71 if ((self = [super init])) 72 { 73 NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 74 [paragraphStyle setLineBreakMode: NSLineBreakByTruncatingTail]; 75 76 fNameAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 77 [NSFont messageFontOfSize: 12.0], NSFontAttributeName, 78 paragraphStyle, NSParagraphStyleAttributeName, nil]; 79 80 fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 81 [NSFont messageFontOfSize: 9.0], NSFontAttributeName, 82 paragraphStyle, NSParagraphStyleAttributeName, nil]; 83 84 } 85 return self; 86} 87 88 89- (id) copyWithZone: (NSZone *) zone 90{ 91 TrackerCell * copy = [super copyWithZone: zone]; 92 93 copy->fNameAttributes = fNameAttributes; 94 copy->fStatusAttributes = fStatusAttributes; 95 96 return copy; 97} 98 99- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) controlView 100{ 101 //icon 102 [[self favIcon] drawInRect: [self imageRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil]; 103 104 //set table colors 105 NSColor * nameColor, * statusColor; 106 if ([self backgroundStyle] == NSBackgroundStyleDark) 107 nameColor = statusColor = [NSColor whiteColor]; 108 else 109 { 110 nameColor = [NSColor labelColor]; 111 statusColor = [NSColor secondaryLabelColor]; 112 } 113 114 fNameAttributes[NSForegroundColorAttributeName] = nameColor; 115 fStatusAttributes[NSForegroundColorAttributeName] = statusColor; 116 117 TrackerNode * node = (TrackerNode *)[self objectValue]; 118 119 //name 120 NSAttributedString * nameString = [self attributedName]; 121 const NSRect nameRect = [self rectForNameWithString: nameString inBounds: cellFrame]; 122 [nameString drawInRect: nameRect]; 123 124 //count strings 125 NSAttributedString * seederString = [self attributedCount: [node totalSeeders]]; 126 const NSRect seederRect = [self rectForCountWithString: seederString withAboveRect: nameRect inBounds: cellFrame]; 127 [seederString drawInRect: seederRect]; 128 129 NSAttributedString * leecherString = [self attributedCount: [node totalLeechers]]; 130 const NSRect leecherRect = [self rectForCountWithString: leecherString withAboveRect: seederRect inBounds: cellFrame]; 131 [leecherString drawInRect: leecherRect]; 132 133 NSAttributedString * downloadedString = [self attributedCount: [node totalDownloaded]]; 134 const NSRect downloadedRect = [self rectForCountWithString: downloadedString withAboveRect: leecherRect inBounds: cellFrame]; 135 [downloadedString drawInRect: downloadedRect]; 136 137 //count label strings 138 NSString * seederLabelBaseString = [NSLocalizedString(@"Seeders", "tracker peer stat") stringByAppendingFormat: @": "]; 139 NSAttributedString * seederLabelString = [self attributedStatusWithString: seederLabelBaseString]; 140 const NSRect seederLabelRect = [self rectForCountLabelWithString: seederLabelString withRightRect: seederRect 141 inBounds: cellFrame]; 142 [seederLabelString drawInRect: seederLabelRect]; 143 144 NSString * leecherLabelBaseString = [NSLocalizedString(@"Leechers", "tracker peer stat") stringByAppendingFormat: @": "]; 145 NSAttributedString * leecherLabelString = [self attributedStatusWithString: leecherLabelBaseString]; 146 const NSRect leecherLabelRect = [self rectForCountLabelWithString: leecherLabelString withRightRect: leecherRect 147 inBounds: cellFrame]; 148 [leecherLabelString drawInRect: leecherLabelRect]; 149 150 NSString * downloadedLabelBaseString = [NSLocalizedString(@"Downloaded", "tracker peer stat") stringByAppendingFormat: @": "]; 151 NSAttributedString * downloadedLabelString = [self attributedStatusWithString: downloadedLabelBaseString]; 152 const NSRect downloadedLabelRect = [self rectForCountLabelWithString: downloadedLabelString withRightRect: downloadedRect 153 inBounds: cellFrame]; 154 [downloadedLabelString drawInRect: downloadedLabelRect]; 155 156 //status strings 157 NSAttributedString * lastAnnounceString = [self attributedStatusWithString: [node lastAnnounceStatusString]]; 158 const NSRect lastAnnounceRect = [self rectForStatusWithString: lastAnnounceString withAboveRect: nameRect 159 withRightRect: seederLabelRect inBounds: cellFrame]; 160 [lastAnnounceString drawInRect: lastAnnounceRect]; 161 162 NSAttributedString * nextAnnounceString = [self attributedStatusWithString: [node nextAnnounceStatusString]]; 163 const NSRect nextAnnounceRect = [self rectForStatusWithString: nextAnnounceString withAboveRect: lastAnnounceRect 164 withRightRect: leecherLabelRect inBounds: cellFrame]; 165 [nextAnnounceString drawInRect: nextAnnounceRect]; 166 167 NSAttributedString * lastScrapeString = [self attributedStatusWithString: [node lastScrapeStatusString]]; 168 const NSRect lastScrapeRect = [self rectForStatusWithString: lastScrapeString withAboveRect: nextAnnounceRect 169 withRightRect: downloadedLabelRect inBounds: cellFrame]; 170 [lastScrapeString drawInRect: lastScrapeRect]; 171} 172 173@end 174 175@implementation TrackerCell (Private) 176 177- (NSImage *) favIcon 178{ 179 id icon = nil; 180 NSURL * address = [NSURL URLWithString: [(TrackerNode *)[self objectValue] fullAnnounceAddress]]; 181 NSString * host; 182 if ((host = [address host])) 183 { 184 //don't try to parse ip address 185 const BOOL separable = !tr_addressIsIP([host UTF8String]); 186 187 NSArray * hostComponents = separable ? [host componentsSeparatedByString: @"."] : nil; 188 189 //let's try getting the tracker address without using any subdomains 190 NSString * baseAddress; 191 if (separable && [hostComponents count] > 1) 192 baseAddress = [NSString stringWithFormat: @"http://%@.%@", 193 hostComponents[[hostComponents count]-2], [hostComponents lastObject]]; 194 else 195 baseAddress = [NSString stringWithFormat: @"http://%@", host]; 196 197 icon = [fTrackerIconCache objectForKey: baseAddress]; 198 if (!icon) 199 [self loadTrackerIcon: baseAddress]; 200 } 201 202 return (icon && icon != [NSNull null]) ? icon : [NSImage imageNamed: @"FavIcon"]; 203} 204 205#warning better favicon detection 206- (void) loadTrackerIcon: (NSString *) baseAddress 207{ 208 if ([fTrackerIconLoading containsObject: baseAddress]) { 209 return; 210 } 211 [fTrackerIconLoading addObject: baseAddress]; 212 213 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 214 NSImage *icon = nil; 215 216 NSArray<NSString *> *filenamesToTry = @[ @"favicon.png", @"favicon.ico" ]; 217 for (NSString *filename in filenamesToTry) { 218 NSURL * favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent:filename]]; 219 220 NSURLRequest * request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy 221 timeoutInterval: 30.0]; 222 223 NSData * iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL]; 224 if (iconData) { 225 icon = [[NSImage alloc] initWithData: iconData]; 226 if (icon) { 227 break; 228 } 229 } 230 } 231 232 dispatch_async(dispatch_get_main_queue(), ^{ 233 if (icon) 234 { 235 [fTrackerIconCache setObject: icon forKey: baseAddress]; 236 237 [[self controlView] setNeedsDisplay: YES]; 238 } 239 else 240 [fTrackerIconCache setObject: [NSNull null] forKey: baseAddress]; 241 242 [fTrackerIconLoading removeObject: baseAddress]; 243 }); 244 }); 245} 246 247- (NSRect) imageRectForBounds: (NSRect) bounds 248{ 249 return NSMakeRect(NSMinX(bounds) + PADDING_HORIZONAL, NSMinY(bounds) + PADDING_ABOVE_ICON, ICON_SIZE, ICON_SIZE); 250} 251 252- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds 253{ 254 NSRect result; 255 result.origin.x = NSMinX(bounds) + PADDING_HORIZONAL + ICON_SIZE + PADDING_BETWEEN_ICON_AND_NAME; 256 result.origin.y = NSMinY(bounds) + PADDING_ABOVE_NAME; 257 258 result.size.height = [string size].height; 259 result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONAL; 260 261 return result; 262} 263 264- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds 265{ 266 return NSMakeRect(NSMaxX(bounds) - PADDING_HORIZONAL - COUNT_WIDTH, 267 NSMaxY(aboveRect) + PADDING_BETWEEN_LINES, 268 COUNT_WIDTH, [string size].height); 269} 270 271- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds 272{ 273 NSRect result = rightRect; 274 result.size.width = [string size].width; 275 result.origin.x -= NSWidth(result); 276 277 return result; 278} 279 280- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect 281 inBounds: (NSRect) bounds 282{ 283 NSRect result; 284 result.origin.x = NSMinX(bounds) + PADDING_STATUS_HORIZONAL; 285 result.origin.y = NSMaxY(aboveRect) + PADDING_BETWEEN_LINES; 286 287 result.size.height = [string size].height; 288 result.size.width = NSMinX(rightRect) - PADDING_BETWEEN_LINES_ON_SAME_LINE - NSMinX(result); 289 290 return result; 291} 292 293- (NSAttributedString *) attributedName 294{ 295 NSString * name = [(TrackerNode *)[self objectValue] host]; 296 return [[NSAttributedString alloc] initWithString: name attributes: fNameAttributes]; 297} 298 299- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString 300{ 301 return [[NSAttributedString alloc] initWithString: statusString attributes: fStatusAttributes]; 302} 303 304- (NSAttributedString *) attributedCount: (NSInteger) count 305{ 306 NSString * countString = count != -1 ? [NSString stringWithFormat: @"%ld", count] : NSLocalizedString(@"N/A", "tracker peer stat"); 307 return [[NSAttributedString alloc] initWithString: countString attributes: fStatusAttributes]; 308} 309 310@end 311