1/****************************************************************************** 2 * Copyright (c) 2005-2019 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> 25 26#import "NSApplicationAdditions.h" 27#import "NSStringAdditions.h" 28 29@interface NSString (Private) 30 31+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed; 32 33+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb; 34 35@end 36 37@implementation NSString (NSStringAdditions) 38 39+ (NSString *) ellipsis 40{ 41 return @"\xE2\x80\xA6"; 42} 43 44- (NSString *) stringByAppendingEllipsis 45{ 46 return [self stringByAppendingString: [NSString ellipsis]]; 47} 48 49#warning use localizedStringWithFormat: directly when 10.9-only and stringsdict translations are in place 50+ (NSString *) formattedUInteger: (NSUInteger) value 51{ 52 return [NSString localizedStringWithFormat: @"%lu", value]; 53} 54 55#warning should we take long long instead? 56+ (NSString *) stringForFileSize: (uint64_t) size 57{ 58 return [NSByteCountFormatter stringFromByteCount: size countStyle: NSByteCountFormatterCountStyleFile]; 59} 60 61#warning should we take long long instead? 62+ (NSString *) stringForFilePartialSize: (uint64_t) partialSize fullSize: (uint64_t) fullSize 63{ 64 NSByteCountFormatter * fileSizeFormatter = [[NSByteCountFormatter alloc] init]; 65 66 NSString * fullString = [fileSizeFormatter stringFromByteCount: fullSize]; 67 68 //figure out the magniture of the two, since we can't rely on comparing the units because of localization and pluralization issues (for example, "1 byte of 2 bytes") 69 BOOL partialUnitsSame; 70 if (partialSize == 0) 71 partialUnitsSame = YES; //we want to just show "0" when we have no partial data, so always set to the same units 72 else 73 { 74 const unsigned int magnitudePartial = log(partialSize)/log(1000); 75 const unsigned int magnitudeFull = fullSize < 1000 ? 0 : log(fullSize)/log(1000); //we have to catch 0 with a special case, so might as well avoid the math for all of magnitude 0 76 partialUnitsSame = magnitudePartial == magnitudeFull; 77 } 78 79 [fileSizeFormatter setIncludesUnit: !partialUnitsSame]; 80 NSString * partialString = [fileSizeFormatter stringFromByteCount: partialSize]; 81 82 83 return [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "file size string"), partialString, fullString]; 84} 85 86+ (NSString *) stringForSpeed: (CGFloat) speed 87{ 88 return [self stringForSpeed: speed 89 kb: NSLocalizedString(@"KB/s", "Transfer speed (kilobytes per second)") 90 mb: NSLocalizedString(@"MB/s", "Transfer speed (megabytes per second)") 91 gb: NSLocalizedString(@"GB/s", "Transfer speed (gigabytes per second)")]; 92} 93 94+ (NSString *) stringForSpeedAbbrev: (CGFloat) speed 95{ 96 return [self stringForSpeed: speed kb: @"K" mb: @"M" gb: @"G"]; 97} 98 99+ (NSString *) stringForRatio: (CGFloat) ratio 100{ 101 //N/A is different than libtransmission's 102 if ((int)ratio == TR_RATIO_NA) 103 return NSLocalizedString(@"N/A", "No Ratio"); 104 else if ((int)ratio == TR_RATIO_INF) 105 return @"\xE2\x88\x9E"; 106 else 107 { 108 if (ratio < 10.0) 109 return [NSString localizedStringWithFormat: @"%.2f", tr_truncd(ratio, 2)]; 110 else if (ratio < 100.0) 111 return [NSString localizedStringWithFormat: @"%.1f", tr_truncd(ratio, 1)]; 112 else 113 return [NSString localizedStringWithFormat: @"%.0f", tr_truncd(ratio, 0)]; 114 } 115} 116 117+ (NSString *) percentString: (CGFloat) progress longDecimals: (BOOL) longDecimals 118{ 119 if (progress >= 1.0) 120 return [NSString localizedStringWithFormat: @"%d%%", 100]; 121 else if (longDecimals) 122 return [NSString localizedStringWithFormat: @"%.2f%%", tr_truncd(progress * 100.0, 2)]; 123 else 124 return [NSString localizedStringWithFormat: @"%.1f%%", tr_truncd(progress * 100.0, 1)]; 125} 126 127+ (NSString *) timeString: (uint64_t) seconds includesTimeRemainingPhrase: (BOOL) includesTimeRemainingPhrase showSeconds: (BOOL) showSeconds 128{ 129 return [NSString timeString: seconds 130 includesTimeRemainingPhrase: includesTimeRemainingPhrase 131 showSeconds: showSeconds 132 maxFields: NSUIntegerMax]; 133} 134 135+ (NSString *) timeString: (uint64_t) seconds includesTimeRemainingPhrase: (BOOL) includesTimeRemainingPhrase showSeconds: (BOOL) showSeconds maxFields: (NSUInteger) max 136{ 137 NSAssert(![NSApp isOnYosemiteOrBetter], @"you should be using NSDateComponentsFormatter on >= 10.10"); 138 NSParameterAssert(max > 0); 139 140 NSMutableArray * timeArray = [NSMutableArray arrayWithCapacity: MIN(max, 5u)]; 141 NSUInteger remaining = seconds; //causes problems for some users when it's a uint64_t 142 143 if (seconds >= 31557600) //official amount of seconds in one year 144 { 145 const NSUInteger years = remaining / 31557600; 146 if (years == 1) 147 [timeArray addObject: NSLocalizedString(@"1 year", "time string")]; 148 else 149 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u years", "time string"), years]]; 150 remaining %= 31557600; 151 --max; 152 } 153 if (max > 0 && seconds >= (24 * 60 * 60)) 154 { 155 const NSUInteger days = remaining / (24 * 60 * 60); 156 if (days == 1) 157 [timeArray addObject: NSLocalizedString(@"1 day", "time string")]; 158 else 159 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u days", "time string"), days]]; 160 remaining %= (24 * 60 * 60); 161 --max; 162 } 163 if (max > 0 && seconds >= (60 * 60)) 164 { 165 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u hr", "time string"), remaining / (60 * 60)]]; 166 remaining %= (60 * 60); 167 --max; 168 } 169 if (max > 0 && (!showSeconds || seconds >= 60)) 170 { 171 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u min", "time string"), remaining / 60]]; 172 remaining %= 60; 173 --max; 174 } 175 if (max > 0 && showSeconds) 176 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u sec", "time string"), remaining]]; 177 178 NSString * timeString = [timeArray componentsJoinedByString: @" "]; 179 180 if (includesTimeRemainingPhrase) { 181 timeString = [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "time remaining string"), timeString]; 182 } 183 184 return timeString; 185} 186 187- (NSComparisonResult) compareNumeric: (NSString *) string 188{ 189 const NSStringCompareOptions comparisonOptions = NSNumericSearch | NSForcedOrderingSearch; 190 return [self compare: string options: comparisonOptions range: NSMakeRange(0, [self length]) locale: [NSLocale currentLocale]]; 191} 192 193- (NSArray *) betterComponentsSeparatedByCharactersInSet: (NSCharacterSet *) separators 194{ 195 NSMutableArray * components = [NSMutableArray array]; 196 197 NSCharacterSet * includededCharSet = [separators invertedSet]; 198 NSUInteger index = 0; 199 const NSUInteger fullLength = [self length]; 200 do 201 { 202 const NSUInteger start = [self rangeOfCharacterFromSet: includededCharSet options: 0 range: NSMakeRange(index, fullLength - index)].location; 203 if (start == NSNotFound) 204 break; 205 206 const NSRange endRange = [self rangeOfCharacterFromSet: separators options: 0 range: NSMakeRange(start, fullLength - start)]; 207 if (endRange.location == NSNotFound) 208 { 209 [components addObject: [self substringFromIndex: start]]; 210 break; 211 } 212 213 [components addObject: [self substringWithRange: NSMakeRange(start, endRange.location - start)]]; 214 215 index = NSMaxRange(endRange); 216 } 217 while (YES); 218 219 return components; 220} 221 222@end 223 224@implementation NSString (Private) 225 226+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed 227{ 228 double convertedSize; 229 NSString * unit; 230 NSUInteger decimals; 231 if (size < pow(1000, 2)) 232 { 233 convertedSize = size / 1000.0; 234 unit = NSLocalizedString(@"KB", "File size - kilobytes"); 235 decimals = convertedSize >= 10.0 ? 0 : 1; 236 } 237 else if (size < pow(1000, 3)) 238 { 239 convertedSize = size / powf(1000.0, 2); 240 unit = NSLocalizedString(@"MB", "File size - megabytes"); 241 decimals = 1; 242 } 243 else if (size < pow(1000, 4)) 244 { 245 convertedSize = size / powf(1000.0, 3); 246 unit = NSLocalizedString(@"GB", "File size - gigabytes"); 247 decimals = 2; 248 } 249 else 250 { 251 convertedSize = size / powf(1000.0, 4); 252 unit = NSLocalizedString(@"TB", "File size - terabytes"); 253 decimals = 2; 254 } 255 256 //match Finder's behavior 257 NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init]; 258 [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle]; 259 [numberFormatter setMinimumFractionDigits: 0]; 260 [numberFormatter setMaximumFractionDigits: decimals]; 261 262 NSString * fileSizeString = [numberFormatter stringFromNumber: @(convertedSize)]; 263 264 if (!notAllowedUnit || ![unit isEqualToString: notAllowedUnit]) 265 fileSizeString = [fileSizeString stringByAppendingFormat: @" %@", unit]; 266 267 if (unitUsed) 268 *unitUsed = unit; 269 270 return fileSizeString; 271} 272 273+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb 274{ 275 if (speed <= 999.95) //0.0 KB/s to 999.9 KB/s 276 return [NSString localizedStringWithFormat: @"%.1f %@", speed, kb]; 277 278 speed /= 1000.0; 279 280 if (speed <= 99.995) //1.00 MB/s to 99.99 MB/s 281 return [NSString localizedStringWithFormat: @"%.2f %@", speed, mb]; 282 else if (speed <= 999.95) //100.0 MB/s to 999.9 MB/s 283 return [NSString localizedStringWithFormat: @"%.1f %@", speed, mb]; 284 else //insane speeds 285 return [NSString localizedStringWithFormat: @"%.2f %@", (speed / 1000.0), gb]; 286} 287 288@end 289