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#import "TrackerNode.h"
24#import "NSApplicationAdditions.h"
25#import "NSStringAdditions.h"
26
27@implementation TrackerNode
28
29#warning remove ivars in header when 64-bit only (or it compiles in 32-bit mode)
30@synthesize torrent = fTorrent;
31
32- (id) initWithTrackerStat: (tr_tracker_stat *) stat torrent: (Torrent *) torrent
33{
34    if ((self = [super init]))
35    {
36        fStat = *stat;
37        fTorrent = torrent; //weak reference
38    }
39
40    return self;
41}
42
43- (NSString *) description
44{
45    return [@"Tracker: " stringByAppendingString: [self fullAnnounceAddress]];
46}
47
48- (id) copyWithZone: (NSZone *) zone
49{
50    //this object is essentially immutable after initial setup
51    return self;
52}
53
54- (BOOL) isEqual: (id) object
55{
56    if (self == object)
57        return YES;
58
59    if (![object isKindOfClass: [self class]])
60        return NO;
61
62    if ([self torrent] != [object torrent])
63        return NO;
64
65    return [self tier] == [object tier] && [[self fullAnnounceAddress] isEqualToString: [object fullAnnounceAddress]];
66}
67
68- (NSString *) host
69{
70    return @(fStat.host);
71}
72
73- (NSString *) fullAnnounceAddress
74{
75    return @(fStat.announce);
76}
77
78- (NSInteger) tier
79{
80    return fStat.tier;
81}
82
83- (NSUInteger) identifier
84{
85    return fStat.id;
86}
87
88- (NSInteger) totalSeeders
89{
90    return fStat.seederCount;
91}
92
93- (NSInteger) totalLeechers
94{
95    return fStat.leecherCount;
96}
97
98- (NSInteger) totalDownloaded
99{
100    return fStat.downloadCount;
101}
102
103- (NSString *) lastAnnounceStatusString
104{
105    NSString * dateString;
106    if (fStat.hasAnnounced)
107    {
108        NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
109        [dateFormatter setDateStyle: NSDateFormatterFullStyle];
110        [dateFormatter setTimeStyle: NSDateFormatterShortStyle];
111        [dateFormatter setDoesRelativeDateFormatting: YES];
112
113        dateString = [dateFormatter stringFromDate: [NSDate dateWithTimeIntervalSince1970: fStat.lastAnnounceTime]];
114    }
115    else
116        dateString = NSLocalizedString(@"N/A", "Tracker last announce");
117
118    NSString * baseString;
119    if (fStat.hasAnnounced && fStat.lastAnnounceTimedOut)
120        baseString = [NSLocalizedString(@"Announce timed out", "Tracker last announce") stringByAppendingFormat: @": %@", dateString];
121    else if (fStat.hasAnnounced && !fStat.lastAnnounceSucceeded)
122    {
123        baseString = NSLocalizedString(@"Announce error", "Tracker last announce");
124
125        NSString * errorString = @(fStat.lastAnnounceResult);
126        if ([errorString isEqualToString: @""])
127            baseString = [baseString stringByAppendingFormat: @": %@", dateString];
128        else
129            baseString = [baseString stringByAppendingFormat: @": %@ - %@", errorString, dateString];
130    }
131    else
132    {
133        baseString = [NSLocalizedString(@"Last Announce", "Tracker last announce") stringByAppendingFormat: @": %@", dateString];
134        if (fStat.hasAnnounced && fStat.lastAnnounceSucceeded && fStat.lastAnnouncePeerCount > 0)
135        {
136            NSString * peerString;
137            if (fStat.lastAnnouncePeerCount == 1)
138                peerString = NSLocalizedString(@"got 1 peer", "Tracker last announce");
139            else
140                peerString = [NSString stringWithFormat: NSLocalizedString(@"got %d peers", "Tracker last announce"),
141                                        fStat.lastAnnouncePeerCount];
142            baseString = [baseString stringByAppendingFormat: @" (%@)", peerString];
143        }
144    }
145
146    return baseString;
147}
148
149- (NSString *) nextAnnounceStatusString
150{
151    switch (fStat.announceState)
152    {
153        case TR_TRACKER_ACTIVE:
154            return [NSLocalizedString(@"Announce in progress", "Tracker next announce") stringByAppendingEllipsis];
155
156        case TR_TRACKER_WAITING:
157        {
158            const NSTimeInterval nextAnnounceTimeLeft = fStat.nextAnnounceTime - [[NSDate date] timeIntervalSince1970];
159
160            NSString *timeString;
161            if ([NSApp isOnYosemiteOrBetter]) {
162                static NSDateComponentsFormatter *formatter;
163                static dispatch_once_t onceToken;
164                dispatch_once(&onceToken, ^{
165                    formatter = [NSDateComponentsFormatter new];
166                    formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;
167                    formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropLeading;
168                    formatter.collapsesLargestUnit = YES;
169                });
170
171                timeString = [formatter stringFromTimeInterval: nextAnnounceTimeLeft];
172            }
173            else {
174                timeString = [NSString timeString: nextAnnounceTimeLeft includesTimeRemainingPhrase: NO showSeconds: YES];
175            }
176            return [NSString stringWithFormat: NSLocalizedString(@"Next announce in %@", "Tracker next announce"),
177                    timeString];
178        }
179        case TR_TRACKER_QUEUED:
180            return [NSLocalizedString(@"Announce is queued", "Tracker next announce") stringByAppendingEllipsis];
181
182        case TR_TRACKER_INACTIVE:
183            return fStat.isBackup ? NSLocalizedString(@"Tracker will be used as a backup", "Tracker next announce")
184                                    : NSLocalizedString(@"Announce not scheduled", "Tracker next announce");
185
186        default:
187            NSAssert1(NO, @"unknown announce state: %d", fStat.announceState);
188            return nil;
189    }
190}
191
192- (NSString *) lastScrapeStatusString
193{
194    NSString * dateString;
195    if (fStat.hasScraped)
196    {
197        NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
198        [dateFormatter setDateStyle: NSDateFormatterFullStyle];
199        [dateFormatter setTimeStyle: NSDateFormatterShortStyle];
200        [dateFormatter setDoesRelativeDateFormatting: YES];
201
202        dateString = [dateFormatter stringFromDate: [NSDate dateWithTimeIntervalSince1970: fStat.lastScrapeTime]];
203    }
204    else
205        dateString = NSLocalizedString(@"N/A", "Tracker last scrape");
206
207    NSString * baseString;
208    if (fStat.hasScraped && fStat.lastScrapeTimedOut)
209        baseString = [NSLocalizedString(@"Scrape timed out", "Tracker last scrape") stringByAppendingFormat: @": %@", dateString];
210    else if (fStat.hasScraped && !fStat.lastScrapeSucceeded)
211    {
212        baseString = NSLocalizedString(@"Scrape error", "Tracker last scrape");
213
214        NSString * errorString = @(fStat.lastScrapeResult);
215        if ([errorString isEqualToString: @""])
216            baseString = [baseString stringByAppendingFormat: @": %@", dateString];
217        else
218            baseString = [baseString stringByAppendingFormat: @": %@ - %@", errorString, dateString];
219    }
220    else
221        baseString = [NSLocalizedString(@"Last Scrape", "Tracker last scrape") stringByAppendingFormat: @": %@", dateString];
222
223    return baseString;
224}
225
226@end
227