2022-01-20 18:27:56 +00:00
|
|
|
// This file Copyright © 2010-2022 Transmission authors and contributors.
|
|
|
|
// It may be used under the MIT (SPDX: MIT) license.
|
|
|
|
// License text can be found in the licenses/ folder.
|
2010-03-06 23:12:30 +00:00
|
|
|
|
2018-09-30 10:37:30 +00:00
|
|
|
#include <libtransmission/transmission.h>
|
|
|
|
#include <libtransmission/utils.h>
|
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
#import "InfoPeersViewController.h"
|
|
|
|
#import "NSStringAdditions.h"
|
|
|
|
#import "PeerProgressIndicatorCell.h"
|
|
|
|
#import "Torrent.h"
|
2012-04-29 00:53:34 +00:00
|
|
|
#import "WebSeedTableView.h"
|
2022-06-06 22:56:05 +00:00
|
|
|
#import "NSImageAdditions.h"
|
2010-03-06 23:12:30 +00:00
|
|
|
|
2016-03-03 22:27:45 +00:00
|
|
|
#define ANIMATION_ID_KEY @"animationId"
|
|
|
|
#define WEB_SEED_ANIMATION_ID @"webSeed"
|
|
|
|
|
2022-03-30 21:52:23 +00:00
|
|
|
@interface InfoPeersViewController ()<CAAnimationDelegate>
|
2022-02-22 16:04:20 +00:00
|
|
|
|
2022-04-29 22:51:40 +00:00
|
|
|
@property(nonatomic, copy) NSArray<Torrent*>* fTorrents;
|
2022-02-22 16:04:20 +00:00
|
|
|
|
|
|
|
@property(nonatomic) BOOL fSet;
|
|
|
|
|
2022-04-29 22:51:40 +00:00
|
|
|
@property(nonatomic) NSMutableArray<NSDictionary*>* fPeers;
|
|
|
|
@property(nonatomic) NSMutableArray<NSDictionary*>* fWebSeeds;
|
2022-02-22 16:04:20 +00:00
|
|
|
|
|
|
|
@property(nonatomic) IBOutlet NSTableView* fPeerTable;
|
|
|
|
@property(nonatomic) IBOutlet WebSeedTableView* fWebSeedTable;
|
|
|
|
|
|
|
|
@property(nonatomic) IBOutlet NSTextField* fConnectedPeersField;
|
|
|
|
|
|
|
|
@property(nonatomic) CGFloat fViewTopMargin;
|
|
|
|
@property(nonatomic) IBOutlet NSLayoutConstraint* fWebSeedTableTopConstraint;
|
2022-04-29 22:51:40 +00:00
|
|
|
@property(nonatomic, readonly) NSArray<NSSortDescriptor*>* peerSortDescriptors;
|
2010-03-06 23:12:30 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)setupInfo;
|
2010-03-06 23:12:30 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)setWebSeedTableHidden:(BOOL)hide animate:(BOOL)animate;
|
2010-03-06 23:12:30 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation InfoPeersViewController
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (instancetype)init
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
if ((self = [super initWithNibName:@"InfoPeersView" bundle:nil]))
|
2010-03-14 03:09:12 +00:00
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
self.title = NSLocalizedString(@"Peers", "Inspector view -> title");
|
2010-03-14 03:09:12 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)awakeFromNib
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
CGFloat const height = [NSUserDefaults.standardUserDefaults floatForKey:@"InspectorContentHeightPeers"];
|
2010-03-14 02:53:22 +00:00
|
|
|
if (height != 0.0)
|
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
NSRect viewRect = self.view.frame;
|
2010-03-14 02:53:22 +00:00
|
|
|
viewRect.size.height = height;
|
2021-08-07 07:27:56 +00:00
|
|
|
self.view.frame = viewRect;
|
2010-03-14 02:53:22 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:56:44 +00:00
|
|
|
//set table header text
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"IP"].headerCell.stringValue = NSLocalizedString(@"IP Address", "inspector -> peer table -> header");
|
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"Client"].headerCell.stringValue = NSLocalizedString(@"Client", "inspector -> peer table -> header");
|
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"DL From"].headerCell.stringValue = NSLocalizedString(@"DL", "inspector -> peer table -> header");
|
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"UL To"].headerCell.stringValue = NSLocalizedString(@"UL", "inspector -> peer table -> header");
|
2021-08-15 09:41:48 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeedTable tableColumnWithIdentifier:@"Address"].headerCell.stringValue = NSLocalizedString(@"Web Seeds", "inspector -> web seed table -> header");
|
|
|
|
[self.fWebSeedTable tableColumnWithIdentifier:@"DL From"].headerCell.stringValue = NSLocalizedString(@"DL", "inspector -> web seed table -> header");
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
//set table header tool tips
|
2022-03-30 21:52:23 +00:00
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"Encryption"].headerToolTip = NSLocalizedString(
|
|
|
|
@"Encrypted Connection",
|
|
|
|
"inspector -> peer table -> header tool tip");
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"Progress"].headerToolTip = NSLocalizedString(@"Available", "inspector -> peer table -> header tool tip");
|
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"DL From"].headerToolTip = NSLocalizedString(@"Downloading From Peer", "inspector -> peer table -> header tool tip");
|
|
|
|
[self.fPeerTable tableColumnWithIdentifier:@"UL To"].headerToolTip = NSLocalizedString(@"Uploading To Peer", "inspector -> peer table -> header tool tip");
|
2021-08-15 09:41:48 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeedTable tableColumnWithIdentifier:@"DL From"].headerToolTip = NSLocalizedString(
|
2021-08-15 09:41:48 +00:00
|
|
|
@"Downloading From Web Seed",
|
2021-08-07 07:27:56 +00:00
|
|
|
"inspector -> web seed table -> header tool tip");
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
//prepare for animating peer table and web seed table
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fViewTopMargin = self.fWebSeedTableTopConstraint.constant;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
CABasicAnimation* webSeedTableAnimation = [CABasicAnimation animation];
|
|
|
|
webSeedTableAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
2021-08-07 07:27:56 +00:00
|
|
|
webSeedTableAnimation.duration = 0.125;
|
|
|
|
webSeedTableAnimation.delegate = self;
|
2021-08-15 09:41:48 +00:00
|
|
|
[webSeedTableAnimation setValue:WEB_SEED_ANIMATION_ID forKey:ANIMATION_ID_KEY];
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fWebSeedTableTopConstraint.animations = @{ @"constant" : webSeedTableAnimation };
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
[self setWebSeedTableHidden:YES animate:NO];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#warning subclass?
|
2022-04-29 22:51:40 +00:00
|
|
|
- (void)setInfoForTorrents:(NSArray<Torrent*>*)torrents
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2010-04-17 18:44:34 +00:00
|
|
|
//don't check if it's the same in case the metadata changed
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fTorrents = torrents;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fSet = NO;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)updateInfo
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (!self.fSet)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2010-03-09 02:26:52 +00:00
|
|
|
[self setupInfo];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fTorrents.count == 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2010-03-06 23:12:30 +00:00
|
|
|
return;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
if (!self.fPeers)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fPeers = [[NSMutableArray alloc] init];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeers removeAllObjects];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
if (!self.fWebSeeds)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fWebSeeds = [[NSMutableArray alloc] init];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeeds removeAllObjects];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSUInteger connected = 0;
|
|
|
|
NSUInteger tracker = 0;
|
|
|
|
NSUInteger incoming = 0;
|
|
|
|
NSUInteger cache = 0;
|
|
|
|
NSUInteger lpd = 0;
|
|
|
|
NSUInteger pex = 0;
|
|
|
|
NSUInteger dht = 0;
|
|
|
|
NSUInteger ltep = 0;
|
|
|
|
NSUInteger toUs = 0;
|
|
|
|
NSUInteger fromUs = 0;
|
2010-03-06 23:12:30 +00:00
|
|
|
BOOL anyActive = false;
|
2022-02-22 16:04:20 +00:00
|
|
|
for (Torrent* torrent in self.fTorrents)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
if (torrent.webSeedCount > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeeds addObjectsFromArray:torrent.webSeeds];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-07 07:27:56 +00:00
|
|
|
if (torrent.active)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
|
|
|
anyActive = YES;
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeers addObjectsFromArray:torrent.peers];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSUInteger const connectedThis = torrent.totalPeersConnected;
|
2010-03-06 23:12:30 +00:00
|
|
|
if (connectedThis > 0)
|
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
connected += torrent.totalPeersConnected;
|
|
|
|
tracker += torrent.totalPeersTracker;
|
|
|
|
incoming += torrent.totalPeersIncoming;
|
|
|
|
cache += torrent.totalPeersCache;
|
|
|
|
lpd += torrent.totalPeersLocal;
|
|
|
|
pex += torrent.totalPeersPex;
|
|
|
|
dht += torrent.totalPeersDHT;
|
|
|
|
ltep += torrent.totalPeersLTEP;
|
|
|
|
|
|
|
|
toUs += torrent.peersSendingToUs;
|
|
|
|
fromUs += torrent.peersGettingFromUs;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeers sortUsingDescriptors:self.peerSortDescriptors];
|
|
|
|
[self.fPeerTable reloadData];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeeds sortUsingDescriptors:self.fWebSeedTable.sortDescriptors];
|
|
|
|
[self.fWebSeedTable reloadData];
|
|
|
|
self.fWebSeedTable.webSeeds = self.fWebSeeds;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
if (anyActive)
|
|
|
|
{
|
2022-05-14 05:31:24 +00:00
|
|
|
NSString* connectedText;
|
|
|
|
if (connected == 1)
|
|
|
|
{
|
|
|
|
connectedText = NSLocalizedString(@"1 Connected", "Inspector -> Peers tab -> peers");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
connectedText = [NSString stringWithFormat:NSLocalizedString(@"%lu Connected", "Inspector -> Peers tab -> peers"), connected];
|
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
if (connected > 0)
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSMutableArray* upDownComponents = [NSMutableArray arrayWithCapacity:2];
|
2011-03-04 02:58:08 +00:00
|
|
|
if (toUs > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[upDownComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"DL from %lu", "Inspector -> Peers tab -> peers"), toUs]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2011-03-04 02:58:08 +00:00
|
|
|
if (fromUs > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[upDownComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"UL to %lu", "Inspector -> Peers tab -> peers"), fromUs]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2021-08-07 07:27:56 +00:00
|
|
|
if (upDownComponents.count > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
connectedText = [connectedText stringByAppendingFormat:@": %@", [upDownComponents componentsJoinedByString:@", "]];
|
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSMutableArray* fromComponents = [NSMutableArray arrayWithCapacity:7];
|
2010-03-06 23:12:30 +00:00
|
|
|
if (tracker > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[fromComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu tracker", "Inspector -> Peers tab -> peers"), tracker]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
if (incoming > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[fromComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu incoming", "Inspector -> Peers tab -> peers"), incoming]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
if (cache > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[fromComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu cache", "Inspector -> Peers tab -> peers"), cache]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-05-19 23:08:14 +00:00
|
|
|
if (lpd > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[fromComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu local discovery", "Inspector -> Peers tab -> peers"), lpd]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
if (pex > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-05-14 05:31:24 +00:00
|
|
|
[fromComponents
|
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu PEX", "Inspector -> Peers tab -> peers"), pex]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
if (dht > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-05-14 05:31:24 +00:00
|
|
|
[fromComponents
|
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu DHT", "Inspector -> Peers tab -> peers"), dht]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
if (ltep > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[fromComponents
|
2022-05-14 05:31:24 +00:00
|
|
|
addObject:[NSString stringWithFormat:NSLocalizedString(@"%lu LTEP", "Inspector -> Peers tab -> peers"), ltep]];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
connectedText = [connectedText stringByAppendingFormat:@"\n%@", [fromComponents componentsJoinedByString:@", "]];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fConnectedPeersField.stringValue = connectedText;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* notActiveString;
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fTorrents.count == 1)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2011-03-04 02:58:08 +00:00
|
|
|
notActiveString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2011-03-04 02:58:08 +00:00
|
|
|
notActiveString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fConnectedPeersField.stringValue = notActiveString;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)saveViewSize
|
2010-03-14 02:53:22 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
[NSUserDefaults.standardUserDefaults setFloat:NSHeight(self.view.frame) forKey:@"InspectorContentHeightPeers"];
|
2010-03-14 02:53:22 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)clearView
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fPeers = nil;
|
|
|
|
self.fWebSeeds = nil;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (tableView == self.fWebSeedTable)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
return self.fWebSeeds ? self.fWebSeeds.count : 0;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
return self.fPeers ? self.fPeers.count : 0;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (id)tableView:(NSTableView*)tableView objectValueForTableColumn:(NSTableColumn*)column row:(NSInteger)row
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (tableView == self.fWebSeedTable)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* ident = column.identifier;
|
2022-02-22 16:04:20 +00:00
|
|
|
NSDictionary* webSeed = self.fWebSeeds[row];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
if ([ident isEqualToString:@"DL From"])
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSNumber* rate;
|
|
|
|
return (rate = webSeed[@"DL From Rate"]) ? [NSString stringForSpeedAbbrev:rate.doubleValue] : @"";
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2017-07-08 14:38:47 +00:00
|
|
|
return webSeed[@"Address"];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* ident = column.identifier;
|
2022-02-22 16:04:20 +00:00
|
|
|
NSDictionary* peer = self.fPeers[row];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
if ([ident isEqualToString:@"Encryption"])
|
|
|
|
{
|
2022-06-06 22:56:05 +00:00
|
|
|
return [peer[@"Encryption"] boolValue] ? [NSImage systemSymbol:@"lock.fill" withFallback:@"Lock"] : nil;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
|
|
|
else if ([ident isEqualToString:@"Client"])
|
|
|
|
{
|
2017-07-08 14:38:47 +00:00
|
|
|
return peer[@"Client"];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
|
|
|
else if ([ident isEqualToString:@"Progress"])
|
|
|
|
{
|
2017-07-08 14:38:47 +00:00
|
|
|
return peer[@"Progress"];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
|
|
|
else if ([ident isEqualToString:@"UL To"])
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSNumber* rate;
|
|
|
|
return (rate = peer[@"UL To Rate"]) ? [NSString stringForSpeedAbbrev:rate.doubleValue] : @"";
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2021-08-15 09:41:48 +00:00
|
|
|
else if ([ident isEqualToString:@"DL From"])
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSNumber* rate;
|
|
|
|
return (rate = peer[@"DL From Rate"]) ? [NSString stringForSpeedAbbrev:rate.doubleValue] : @"";
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2017-07-08 14:38:47 +00:00
|
|
|
return peer[@"IP"];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)tableView:(NSTableView*)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (tableView == self.fPeerTable)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* ident = tableColumn.identifier;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
if ([ident isEqualToString:@"Progress"])
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
NSDictionary* peer = self.fPeers[row];
|
|
|
|
((PeerProgressIndicatorCell*)cell).seed = [peer[@"Seed"] boolValue];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn*)tableColumn
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (tableView == self.fWebSeedTable)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fWebSeeds)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeeds sortUsingDescriptors:self.fWebSeedTable.sortDescriptors];
|
2010-03-06 23:12:30 +00:00
|
|
|
[tableView reloadData];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fPeers)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fPeers sortUsingDescriptors:self.peerSortDescriptors];
|
2010-03-06 23:12:30 +00:00
|
|
|
[tableView reloadData];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (BOOL)tableView:(NSTableView*)tableView shouldSelectRow:(NSInteger)row
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
return tableView != self.fPeerTable;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (NSString*)tableView:(NSTableView*)tableView
|
|
|
|
toolTipForCell:(NSCell*)cell
|
|
|
|
rect:(NSRectPointer)rect
|
|
|
|
tableColumn:(NSTableColumn*)column
|
|
|
|
row:(NSInteger)row
|
|
|
|
mouseLocation:(NSPoint)mouseLocation
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (tableView == self.fPeerTable)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
BOOL const multiple = self.fTorrents.count > 1;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
NSDictionary* peer = self.fPeers[row];
|
2021-08-15 09:41:48 +00:00
|
|
|
NSMutableArray* components = [NSMutableArray arrayWithCapacity:multiple ? 6 : 5];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
if (multiple)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
[components addObject:peer[@"Name"]];
|
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
CGFloat const progress = [peer[@"Progress"] floatValue];
|
|
|
|
NSString* progressString = [NSString stringWithFormat:NSLocalizedString(@"Progress: %@", "Inspector -> Peers tab -> table row tooltip"),
|
|
|
|
[NSString percentString:progress longDecimals:NO]];
|
2017-07-08 14:38:47 +00:00
|
|
|
if (progress < 1.0 && [peer[@"Seed"] boolValue])
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
progressString = [progressString
|
|
|
|
stringByAppendingFormat:@" (%@)", NSLocalizedString(@"Partial Seed", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
}
|
|
|
|
[components addObject:progressString];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* protocolString = [peer[@"uTP"] boolValue] ? @"\u00b5TP" : @"TCP";
|
2017-07-08 14:38:47 +00:00
|
|
|
if ([peer[@"Encryption"] boolValue])
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
protocolString = [protocolString
|
|
|
|
stringByAppendingFormat:@" (%@)", NSLocalizedString(@"encrypted", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
}
|
|
|
|
[components addObject:[NSString stringWithFormat:NSLocalizedString(@"Protocol: %@", "Inspector -> Peers tab -> table row tooltip"),
|
|
|
|
protocolString]];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* portString;
|
2010-03-06 23:12:30 +00:00
|
|
|
NSInteger port;
|
2017-07-08 14:38:47 +00:00
|
|
|
if ((port = [peer[@"Port"] intValue]) > 0)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
|
|
|
portString = [NSString stringWithFormat:@"%ld", port];
|
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2010-03-06 23:12:30 +00:00
|
|
|
portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
|
|
|
[components addObject:[NSString stringWithFormat:@"%@: %@",
|
|
|
|
NSLocalizedString(@"Port", "Inspector -> Peers tab -> table row tooltip"),
|
|
|
|
portString]];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
NSInteger const peerFrom = [peer[@"From"] integerValue];
|
2010-03-06 23:12:30 +00:00
|
|
|
switch (peerFrom)
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
case TR_PEER_FROM_TRACKER:
|
|
|
|
[components addObject:NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_INCOMING:
|
|
|
|
[components addObject:NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_RESUME:
|
|
|
|
[components addObject:NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_LPD:
|
|
|
|
[components addObject:NSLocalizedString(@"From: local peer discovery", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_PEX:
|
|
|
|
[components addObject:NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_DHT:
|
|
|
|
[components addObject:NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
case TR_PEER_FROM_LTEP:
|
|
|
|
[components addObject:NSLocalizedString(@"From: libtorrent extension protocol handshake", "Inspector -> Peers tab -> table row tooltip")];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NSAssert1(NO, @"Peer from unknown source: %ld", peerFrom);
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
//determing status strings from flags
|
2021-08-15 09:41:48 +00:00
|
|
|
NSMutableArray* statusArray = [NSMutableArray arrayWithCapacity:6];
|
|
|
|
NSString* flags = peer[@"Flags"];
|
|
|
|
|
|
|
|
if ([flags rangeOfString:@"D"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(@"Currently downloading (interested and not choked)", "Inspector -> peer -> status")];
|
|
|
|
}
|
|
|
|
if ([flags rangeOfString:@"d"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(
|
|
|
|
@"You want to download, but peer does not want to send (interested and choked)",
|
|
|
|
"Inspector -> peer -> status")];
|
|
|
|
}
|
|
|
|
if ([flags rangeOfString:@"U"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(@"Currently uploading (interested and not choked)", "Inspector -> peer -> status")];
|
|
|
|
}
|
|
|
|
if ([flags rangeOfString:@"u"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)", "Inspector -> peer -> status")];
|
|
|
|
}
|
|
|
|
if ([flags rangeOfString:@"K"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(@"Peer is unchoking you, but you are not interested", "Inspector -> peer -> status")];
|
|
|
|
}
|
|
|
|
if ([flags rangeOfString:@"?"].location != NSNotFound)
|
|
|
|
{
|
|
|
|
[statusArray addObject:NSLocalizedString(@"You unchoked the peer, but the peer is not interested", "Inspector -> peer -> status")];
|
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-07 07:27:56 +00:00
|
|
|
if (statusArray.count > 0)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSString* statusStrings = [statusArray componentsJoinedByString:@"\n\n"];
|
|
|
|
[components addObject:[@"\n" stringByAppendingString:statusStrings]];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
return [components componentsJoinedByString:@"\n"];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fTorrents.count > 1)
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
return self.fWebSeeds[row][@"Name"];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)animationDidStart:(CAAnimation*)animation
|
2016-03-03 22:27:45 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
if (![[animation valueForKey:ANIMATION_ID_KEY] isEqualToString:WEB_SEED_ANIMATION_ID])
|
|
|
|
{
|
2016-03-03 22:27:45 +00:00
|
|
|
return;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2016-03-03 22:27:45 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fWebSeedTable.enclosingScrollView.hidden = NO;
|
2016-03-03 22:27:45 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished
|
2016-03-03 22:27:45 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
if (![[animation valueForKey:ANIMATION_ID_KEY] isEqualToString:WEB_SEED_ANIMATION_ID])
|
|
|
|
{
|
2016-03-03 22:27:45 +00:00
|
|
|
return;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2016-03-03 22:27:45 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fWebSeedTable.enclosingScrollView.hidden = finished && self.fWebSeedTableTopConstraint.constant < 0;
|
2016-03-03 22:27:45 +00:00
|
|
|
}
|
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
#pragma mark - Private
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)setupInfo
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2012-04-29 00:53:34 +00:00
|
|
|
__block BOOL hasWebSeeds = NO;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
if (self.fTorrents.count == 0)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fPeers = nil;
|
|
|
|
[self.fPeerTable reloadData];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fConnectedPeersField.stringValue = @"";
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fTorrents enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(Torrent* torrent, NSUInteger idx, BOOL* stop) {
|
2021-08-07 07:27:56 +00:00
|
|
|
if (torrent.webSeedCount > 0)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
|
|
|
hasWebSeeds = YES;
|
2012-04-29 00:53:34 +00:00
|
|
|
*stop = YES;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2012-04-29 00:53:34 +00:00
|
|
|
}];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
if (!hasWebSeeds)
|
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fWebSeeds = nil;
|
|
|
|
[self.fWebSeedTable reloadData];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2012-04-29 00:53:34 +00:00
|
|
|
else
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-02-22 16:04:20 +00:00
|
|
|
[self.fWebSeedTable deselectAll:self];
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
|
|
|
[self setWebSeedTableHidden:!hasWebSeeds animate:YES];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
self.fSet = YES;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
- (void)setWebSeedTableHidden:(BOOL)hide animate:(BOOL)animate
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
if (animate && (!self.view.window || !self.view.window.visible))
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2010-03-06 23:12:30 +00:00
|
|
|
animate = NO;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2016-02-27 20:29:57 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
CGFloat const webSeedTableTopMargin = hide ? -NSHeight(self.fWebSeedTable.enclosingScrollView.frame) : self.fViewTopMargin;
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
(animate ? [self.fWebSeedTableTopConstraint animator] : self.fWebSeedTableTopConstraint).constant = webSeedTableTopMargin;
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
|
|
|
|
2022-04-29 22:51:40 +00:00
|
|
|
- (NSArray<NSSortDescriptor*>*)peerSortDescriptors
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSMutableArray* descriptors = [NSMutableArray arrayWithCapacity:2];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2022-02-22 16:04:20 +00:00
|
|
|
NSArray* oldDescriptors = self.fPeerTable.sortDescriptors;
|
2010-03-06 23:12:30 +00:00
|
|
|
BOOL useSecond = YES, asc = YES;
|
2021-08-07 07:27:56 +00:00
|
|
|
if (oldDescriptors.count > 0)
|
2010-03-06 23:12:30 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSSortDescriptor* descriptor = oldDescriptors[0];
|
|
|
|
[descriptors addObject:descriptor];
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
if ((useSecond = ![descriptor.key isEqualToString:@"IP"]))
|
|
|
|
{
|
2021-08-07 07:27:56 +00:00
|
|
|
asc = descriptor.ascending;
|
2021-08-15 09:41:48 +00:00
|
|
|
}
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
//sort by IP after primary sort
|
|
|
|
if (useSecond)
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
NSSortDescriptor* secondDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"IP" ascending:asc
|
|
|
|
selector:@selector(compareNumeric:)];
|
|
|
|
[descriptors addObject:secondDescriptor];
|
2010-03-06 23:12:30 +00:00
|
|
|
}
|
2017-01-24 17:53:16 +00:00
|
|
|
|
2010-03-06 23:12:30 +00:00
|
|
|
return descriptors;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|