Merge nat-traversal branch to trunk.

This commit is contained in:
Josh Elsasser 2006-09-25 18:37:45 +00:00
parent bec163be16
commit 0257761670
70 changed files with 5227 additions and 683 deletions

28
PiecesWindowController.h Normal file
View File

@ -0,0 +1,28 @@
//
// PiecesWindowController.h
// Transmission
//
// Created by Livingston on 9/23/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Torrent.h"
@interface PiecesWindowController : NSWindowController
{
int8_t * fPieces;
NSImage * fExistingImage, * fBack, * fWhitePiece, * fGreenPiece,
* fBlue1Piece, * fBlue2Piece, * fBlue3Piece;
Torrent * fTorrent;
int fNumPieces, fAcross, fWidth, fExtraBorder;
IBOutlet NSImageView * fImageView;
}
- (void) setTorrent: (Torrent *) torrent;
- (void) updateView: (BOOL) first;
@end

214
PiecesWindowController.m Normal file
View File

@ -0,0 +1,214 @@
//
// PiecesWindowController.m
// Transmission
//
// Created by Livingston on 9/23/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
#import "PiecesWindowController.h"
#define MAX_ACROSS 20
#define BETWEEN 1.0
#define BLANK -99
@implementation PiecesWindowController
- (id) initWithWindowNibName: (NSString *) name
{
if ((self = [super initWithWindowNibName: name]))
{
fTorrent = nil;
int numPieces = MAX_ACROSS * MAX_ACROSS;
fPieces = malloc(numPieces);
int i;
for (i = 0; i < numPieces; i++)
fPieces[i] = BLANK;
fBack = [NSImage imageNamed: @"PiecesBack.tiff"];
NSSize size = [fBack size];
fWhitePiece = [NSImage imageNamed: @"BoxWhite.tiff"];
[fWhitePiece setScalesWhenResized: YES];
[fWhitePiece setSize: size];
fGreenPiece = [NSImage imageNamed: @"BoxGreen.tiff"];
[fGreenPiece setScalesWhenResized: YES];
[fGreenPiece setSize: size];
fBlue1Piece = [NSImage imageNamed: @"BoxBlue1.tiff"];
[fBlue1Piece setScalesWhenResized: YES];
[fBlue1Piece setSize: size];
fBlue2Piece = [NSImage imageNamed: @"BoxBlue2.tiff"];
[fBlue2Piece setScalesWhenResized: YES];
[fBlue2Piece setSize: size];
fBlue3Piece = [NSImage imageNamed: @"BoxBlue3.tiff"];
[fBlue3Piece setScalesWhenResized: YES];
[fBlue3Piece setSize: size];
fExistingImage = [fBack copy];
}
return self;
}
- (void) awakeFromNib
{
//window location and size
NSPanel * window = (NSPanel *)[self window];
[window setBecomesKeyOnlyIfNeeded: YES];
[window setFrameAutosaveName: @"PiecesWindowFrame"];
[window setFrameUsingName: @"PiecesWindowFrame"];
}
- (void) dealloc
{
free(fPieces);
if (fTorrent)
[fTorrent release];
[fExistingImage release];
[super dealloc];
}
- (void) setTorrent: (Torrent *) torrent
{
if (fTorrent)
{
[fTorrent release];
if (!torrent)
{
fTorrent = nil;
[fImageView setImage: fBack];
}
}
if (torrent)
{
fTorrent = [torrent retain];
//determine relevant values
fNumPieces = MAX_ACROSS * MAX_ACROSS;
if ([fTorrent pieceCount] < fNumPieces)
{
fNumPieces = [fTorrent pieceCount];
fAcross = sqrt(fNumPieces);
if (fAcross * fAcross < fNumPieces)
fAcross++;
}
else
fAcross = MAX_ACROSS;
fWidth = ([fExistingImage size].width - (fAcross + 1) * BETWEEN) / fAcross;
fExtraBorder = ([fExistingImage size].width - ((fWidth + BETWEEN) * fAcross + BETWEEN)) / 2;
[self updateView: YES];
}
}
- (void) updateView: (BOOL) first
{
if (!fTorrent)
return;
if (first)
{
[fExistingImage release];
fExistingImage = [fBack copy];
}
int8_t * pieces = malloc(fNumPieces);
[fTorrent getAvailability: pieces size: fNumPieces];
int i, j, piece, index = -1;
NSPoint point;
NSRect rect = NSMakeRect(0, 0, fWidth, fWidth);
NSImage * pieceImage;
BOOL change = NO;
for (i = 0; i < fAcross; i++)
for (j = 0; j < fAcross; j++)
{
index++;
if (index >= fNumPieces)
break;
pieceImage = nil;
piece = pieces[index];
if (piece < 0)
{
if (first || fPieces[index] != -1)
{
fPieces[index] = -1;
pieceImage = fGreenPiece;
}
}
else if (piece == 0)
{
if (first || fPieces[index] != 0)
{
fPieces[index] = 0;
pieceImage = fWhitePiece;
}
}
else if (piece == 1)
{
if (first || fPieces[index] != 1)
{
fPieces[index] = 1;
pieceImage = fBlue1Piece;
}
}
else if (piece == 2)
{
if (first || fPieces[index] != 2)
{
fPieces[index] = 2;
pieceImage = fBlue2Piece;
}
}
else
{
if (first || fPieces[index] != 3)
{
fPieces[index] = 3;
pieceImage = fBlue3Piece;
}
}
if (pieceImage)
{
//drawing actually will occur, so figure out values
if (!change)
{
[fExistingImage lockFocus];
change = YES;
}
point = NSMakePoint(j * (fWidth + BETWEEN) + BETWEEN + fExtraBorder,
[fExistingImage size].width - (i + 1) * (fWidth + BETWEEN) - fExtraBorder);
[pieceImage compositeToPoint: point fromRect: rect operation: NSCompositeSourceOver];
}
}
if (change)
{
[fExistingImage unlockFocus];
[fImageView setImage: nil];
[fImageView setImage: fExistingImage];
}
free(pieces);
}
@end

View File

@ -7,6 +7,28 @@
objects = {
/* Begin PBXBuildFile section */
3518E4A50AC620FC002ED3A2 /* PiecesWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3518E4A30AC620FC002ED3A2 /* PiecesWindowController.h */; };
3518E4A60AC620FC002ED3A2 /* PiecesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3518E4A40AC620FC002ED3A2 /* PiecesWindowController.m */; };
3518E4D10AC62517002ED3A2 /* PiecesBack.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E4CD0AC62517002ED3A2 /* PiecesBack.tiff */; };
3518E4D30AC62517002ED3A2 /* BoxBlue1.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E4CF0AC62517002ED3A2 /* BoxBlue1.tiff */; };
3518E4D40AC62517002ED3A2 /* BoxBlue2.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E4D00AC62517002ED3A2 /* BoxBlue2.tiff */; };
3518E4D70AC6253F002ED3A2 /* PiecesWindow.nib in Resources */ = {isa = PBXBuildFile; fileRef = 3518E4D50AC6253F002ED3A2 /* PiecesWindow.nib */; };
3518E4FB0AC62832002ED3A2 /* PiecesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3518E4A40AC620FC002ED3A2 /* PiecesWindowController.m */; };
3518E5210AC62A29002ED3A2 /* PiecesBack.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E4CD0AC62517002ED3A2 /* PiecesBack.tiff */; };
3518E5230AC62A2A002ED3A2 /* BoxBlue2.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E4D00AC62517002ED3A2 /* BoxBlue2.tiff */; };
3518E5240AC62A2B002ED3A2 /* BoxBlue1.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E4CF0AC62517002ED3A2 /* BoxBlue1.tiff */; };
3518E5290AC62A55002ED3A2 /* BoxBlue3.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E5270AC62A55002ED3A2 /* BoxBlue3.tiff */; };
3518E52A0AC62A55002ED3A2 /* BoxWhite.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E5280AC62A55002ED3A2 /* BoxWhite.tiff */; };
3518E52B0AC62A57002ED3A2 /* BoxWhite.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E5280AC62A55002ED3A2 /* BoxWhite.tiff */; };
3518E52C0AC62A57002ED3A2 /* BoxBlue3.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E5270AC62A55002ED3A2 /* BoxBlue3.tiff */; };
3518E5770AC63262002ED3A2 /* BoxGreen.tiff in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3518E5760AC63262002ED3A2 /* BoxGreen.tiff */; };
3518E57B0AC632EA002ED3A2 /* BoxGreen.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3518E5760AC63262002ED3A2 /* BoxGreen.tiff */; };
35B037A70AC59BC600A10FDF /* Check.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 35B037A60AC59BC600A10FDF /* Check.png */; };
35B037B60AC59C4000A10FDF /* Check.png in Resources */ = {isa = PBXBuildFile; fileRef = 35B037A60AC59BC600A10FDF /* Check.png */; };
35B037FB0AC5B53800A10FDF /* ResumeNoWaitOn.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 35B037F90AC5B53800A10FDF /* ResumeNoWaitOn.png */; };
35B037FC0AC5B53800A10FDF /* ResumeNoWaitOff.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 35B037FA0AC5B53800A10FDF /* ResumeNoWaitOff.png */; };
35B038130AC5B6EB00A10FDF /* ResumeNoWaitOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 35B037F90AC5B53800A10FDF /* ResumeNoWaitOn.png */; };
35B038140AC5B6EC00A10FDF /* ResumeNoWaitOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 35B037FA0AC5B53800A10FDF /* ResumeNoWaitOff.png */; };
4D043A7F090AE979009FEDA8 /* TransmissionDocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */; };
4D118E1A08CB46B20033958F /* PrefsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D118E1908CB46B20033958F /* PrefsController.m */; };
4D1838BC09DEC0430047D688 /* internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D18389D09DEC0430047D688 /* internal.h */; };
@ -56,6 +78,14 @@
4DA6FDBB0911233800450CB1 /* PauseOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDB90911233800450CB1 /* PauseOff.png */; };
4DA6FDC5091141AD00450CB1 /* ResumeOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDC3091141AD00450CB1 /* ResumeOff.png */; };
4DA6FDC6091141AD00450CB1 /* ResumeOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDC4091141AD00450CB1 /* ResumeOn.png */; };
4DAB87C50ABE1F730081CF7E /* xml.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DAB87BD0ABE1F730081CF7E /* xml.h */; };
4DAB87C60ABE1F730081CF7E /* xml.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DAB87BE0ABE1F730081CF7E /* xml.c */; };
4DAB87C70ABE1F730081CF7E /* upnp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DAB87BF0ABE1F730081CF7E /* upnp.h */; };
4DAB87C80ABE1F730081CF7E /* upnp.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DAB87C00ABE1F730081CF7E /* upnp.c */; };
4DAB87C90ABE1F730081CF7E /* natpmp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DAB87C10ABE1F730081CF7E /* natpmp.h */; };
4DAB87CA0ABE1F730081CF7E /* natpmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DAB87C20ABE1F730081CF7E /* natpmp.c */; };
4DAB87CB0ABE1F730081CF7E /* http.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DAB87C30ABE1F730081CF7E /* http.h */; };
4DAB87CC0ABE1F730081CF7E /* http.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DAB87C40ABE1F730081CF7E /* http.c */; };
4DCCBB3E09C3D71100D3CABF /* TorrentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DCCBB3C09C3D71100D3CABF /* TorrentCell.m */; };
4DDBB71C09E16BF100284745 /* transmissioncli.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DDBB71B09E16BF100284745 /* transmissioncli.c */; };
4DDFDD22099A5D8E00189D81 /* DownloadBadge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DDFDD20099A5D8E00189D81 /* DownloadBadge.png */; };
@ -178,6 +208,15 @@
files = (
A25FCDDF0A37695F002BCBBE /* PauseSelected.png in CopyFiles */,
A25FCDE00A37695F002BCBBE /* ResumeSelected.png in CopyFiles */,
35B037A70AC59BC600A10FDF /* Check.png in CopyFiles */,
35B037FB0AC5B53800A10FDF /* ResumeNoWaitOn.png in CopyFiles */,
35B037FC0AC5B53800A10FDF /* ResumeNoWaitOff.png in CopyFiles */,
3518E4D10AC62517002ED3A2 /* PiecesBack.tiff in CopyFiles */,
3518E4D30AC62517002ED3A2 /* BoxBlue1.tiff in CopyFiles */,
3518E4D40AC62517002ED3A2 /* BoxBlue2.tiff in CopyFiles */,
3518E5290AC62A55002ED3A2 /* BoxBlue3.tiff in CopyFiles */,
3518E52A0AC62A55002ED3A2 /* BoxWhite.tiff in CopyFiles */,
3518E5770AC63262002ED3A2 /* BoxGreen.tiff in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -210,6 +249,18 @@
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Transmission_Prefix.pch; path = macosx/Transmission_Prefix.pch; sourceTree = "<group>"; };
3518E4A30AC620FC002ED3A2 /* PiecesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PiecesWindowController.h; sourceTree = "<group>"; };
3518E4A40AC620FC002ED3A2 /* PiecesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PiecesWindowController.m; sourceTree = "<group>"; };
3518E4CD0AC62517002ED3A2 /* PiecesBack.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = PiecesBack.tiff; path = macosx/Images/PiecesBack.tiff; sourceTree = "<group>"; };
3518E4CF0AC62517002ED3A2 /* BoxBlue1.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = BoxBlue1.tiff; path = macosx/Images/BoxBlue1.tiff; sourceTree = "<group>"; };
3518E4D00AC62517002ED3A2 /* BoxBlue2.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = BoxBlue2.tiff; path = macosx/Images/BoxBlue2.tiff; sourceTree = "<group>"; };
3518E4D60AC6253F002ED3A2 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = macosx/English.lproj/PiecesWindow.nib; sourceTree = "<group>"; };
3518E5270AC62A55002ED3A2 /* BoxBlue3.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = BoxBlue3.tiff; path = macosx/Images/BoxBlue3.tiff; sourceTree = "<group>"; };
3518E5280AC62A55002ED3A2 /* BoxWhite.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = BoxWhite.tiff; path = macosx/Images/BoxWhite.tiff; sourceTree = "<group>"; };
3518E5760AC63262002ED3A2 /* BoxGreen.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = BoxGreen.tiff; path = macosx/Images/BoxGreen.tiff; sourceTree = "<group>"; };
35B037A60AC59BC600A10FDF /* Check.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Check.png; path = macosx/Images/Check.png; sourceTree = "<group>"; };
35B037F90AC5B53800A10FDF /* ResumeNoWaitOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeNoWaitOn.png; path = macosx/Images/ResumeNoWaitOn.png; sourceTree = "<group>"; };
35B037FA0AC5B53800A10FDF /* ResumeNoWaitOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeNoWaitOff.png; path = macosx/Images/ResumeNoWaitOff.png; sourceTree = "<group>"; };
4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = TransmissionDocument.icns; path = macosx/Images/TransmissionDocument.icns; sourceTree = "<group>"; };
4D118E1808CB46B20033958F /* PrefsController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = PrefsController.h; path = macosx/PrefsController.h; sourceTree = "<group>"; };
4D118E1908CB46B20033958F /* PrefsController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = PrefsController.m; path = macosx/PrefsController.m; sourceTree = "<group>"; };
@ -262,9 +313,17 @@
4DA6FDB90911233800450CB1 /* PauseOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = PauseOff.png; path = macosx/Images/PauseOff.png; sourceTree = "<group>"; };
4DA6FDC3091141AD00450CB1 /* ResumeOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeOff.png; path = macosx/Images/ResumeOff.png; sourceTree = "<group>"; };
4DA6FDC4091141AD00450CB1 /* ResumeOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeOn.png; path = macosx/Images/ResumeOn.png; sourceTree = "<group>"; };
4DAB87BD0ABE1F730081CF7E /* xml.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = xml.h; path = libtransmission/xml.h; sourceTree = "<group>"; };
4DAB87BE0ABE1F730081CF7E /* xml.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = xml.c; path = libtransmission/xml.c; sourceTree = "<group>"; };
4DAB87BF0ABE1F730081CF7E /* upnp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = upnp.h; path = libtransmission/upnp.h; sourceTree = "<group>"; };
4DAB87C00ABE1F730081CF7E /* upnp.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = upnp.c; path = libtransmission/upnp.c; sourceTree = "<group>"; };
4DAB87C10ABE1F730081CF7E /* natpmp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = natpmp.h; path = libtransmission/natpmp.h; sourceTree = "<group>"; };
4DAB87C20ABE1F730081CF7E /* natpmp.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = natpmp.c; path = libtransmission/natpmp.c; sourceTree = "<group>"; };
4DAB87C30ABE1F730081CF7E /* http.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = http.h; path = libtransmission/http.h; sourceTree = "<group>"; };
4DAB87C40ABE1F730081CF7E /* http.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = http.c; path = libtransmission/http.c; sourceTree = "<group>"; };
4DCCBB3C09C3D71100D3CABF /* TorrentCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = TorrentCell.m; path = macosx/TorrentCell.m; sourceTree = "<group>"; };
4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = TorrentCell.h; path = macosx/TorrentCell.h; sourceTree = "<group>"; };
4DDBB71909E16BAE00284745 /* transmissioncli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = transmissioncli; sourceTree = BUILT_PRODUCTS_DIR; };
4DDBB71909E16BAE00284745 /* transmissioncli */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "compiled.mach-o.executable"; path = transmissioncli; sourceTree = BUILT_PRODUCTS_DIR; };
4DDBB71B09E16BF100284745 /* transmissioncli.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = transmissioncli.c; path = cli/transmissioncli.c; sourceTree = "<group>"; };
4DDFDD20099A5D8E00189D81 /* DownloadBadge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = DownloadBadge.png; path = macosx/Images/DownloadBadge.png; sourceTree = "<group>"; };
4DDFDD21099A5D8E00189D81 /* UploadBadge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = UploadBadge.png; path = macosx/Images/UploadBadge.png; sourceTree = "<group>"; };
@ -393,6 +452,8 @@
080E96DDFE201D6D7F000001 /* Sources */ = {
isa = PBXGroup;
children = (
3518E4A30AC620FC002ED3A2 /* PiecesWindowController.h */,
3518E4A40AC620FC002ED3A2 /* PiecesWindowController.m */,
A2A306530AAD24A80049E2AC /* UKFileWatcher.h */,
A2A306540AAD24A80049E2AC /* UKFileWatcher.m */,
A2A306550AAD24A80049E2AC /* UKFNSubscribeFileWatcher.h */,
@ -463,6 +524,12 @@
children = (
A259316A0A73B2CC002F4FE7 /* Transmission Help */,
A2F8951E0A2D4BA500ED2127 /* Credits.rtf */,
3518E4CD0AC62517002ED3A2 /* PiecesBack.tiff */,
3518E4CF0AC62517002ED3A2 /* BoxBlue1.tiff */,
3518E4D00AC62517002ED3A2 /* BoxBlue2.tiff */,
3518E5270AC62A55002ED3A2 /* BoxBlue3.tiff */,
3518E5280AC62A55002ED3A2 /* BoxWhite.tiff */,
3518E5760AC63262002ED3A2 /* BoxGreen.tiff */,
A2305AA40A3DCCEF00AB2D77 /* ProgressBarEndAdvanced.png */,
A2305AA50A3DCCEF00AB2D77 /* ProgressBarEndBlue.png */,
A2305AA60A3DCCEF00AB2D77 /* ProgressBarEndWhite.png */,
@ -473,6 +540,7 @@
A2305A7D0A3DC9E400AB2D77 /* ProgressBarBlue.png */,
A2305A7E0A3DC9E400AB2D77 /* ProgressBarGray.png */,
A2305A7F0A3DC9E400AB2D77 /* ProgressBarGreen.png */,
35B037A60AC59BC600A10FDF /* Check.png */,
A260C9AB0AA3B8D700FDC1B7 /* Error.tiff */,
A2D4F0840A915F7200890C32 /* GreenDot.tiff */,
A2D4F0820A915F6600890C32 /* RedDot.tiff */,
@ -486,6 +554,8 @@
4DF7500808A103AD007B0D70 /* Info.png */,
4DF7500708A103AD007B0D70 /* Open.png */,
4DF7500908A103AD007B0D70 /* Remove.png */,
35B037F90AC5B53800A10FDF /* ResumeNoWaitOn.png */,
35B037FA0AC5B53800A10FDF /* ResumeNoWaitOff.png */,
4D6DAAC4090CE00500F43C22 /* RevealOff.png */,
4D6DAAC5090CE00500F43C22 /* RevealOn.png */,
4DA6FDB80911233800450CB1 /* PauseOn.png */,
@ -529,6 +599,7 @@
A253F7280A699373008EE24F /* FilterButtonSelectedMain.png */,
A253F7290A699373008EE24F /* FilterButtonSelectedRight.png */,
A2912C520A2956E80097A0CA /* PrefsWindow.nib */,
3518E4D50AC6253F002ED3A2 /* PiecesWindow.nib */,
);
name = Resources;
sourceTree = "<group>";
@ -583,6 +654,14 @@
4D1838B909DEC0430047D688 /* completion.h */,
4D1838BA09DEC0430047D688 /* completion.c */,
4D1838BB09DEC0430047D688 /* clients.h */,
4DAB87BD0ABE1F730081CF7E /* xml.h */,
4DAB87BE0ABE1F730081CF7E /* xml.c */,
4DAB87BF0ABE1F730081CF7E /* upnp.h */,
4DAB87C00ABE1F730081CF7E /* upnp.c */,
4DAB87C10ABE1F730081CF7E /* natpmp.h */,
4DAB87C20ABE1F730081CF7E /* natpmp.c */,
4DAB87C30ABE1F730081CF7E /* http.h */,
4DAB87C40ABE1F730081CF7E /* http.c */,
);
name = libtransmission;
sourceTree = "<group>";
@ -649,6 +728,11 @@
4D1838FC09DEC4380047D688 /* transmission.h in Headers */,
A26D450B0A0503AC00A10BB3 /* peermessages.h in Headers */,
A2BD40070A09BBEA008CCE96 /* bencode.h in Headers */,
4DAB87C50ABE1F730081CF7E /* xml.h in Headers */,
4DAB87C70ABE1F730081CF7E /* upnp.h in Headers */,
4DAB87C90ABE1F730081CF7E /* natpmp.h in Headers */,
4DAB87CB0ABE1F730081CF7E /* http.h in Headers */,
3518E4A50AC620FC002ED3A2 /* PiecesWindowController.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -799,6 +883,16 @@
A2D4F0850A915F7200890C32 /* GreenDot.tiff in Resources */,
A21567ED0A9A5034004DECD6 /* MessageWindow.nib in Resources */,
A260C9AC0AA3B8D700FDC1B7 /* Error.tiff in Resources */,
35B037B60AC59C4000A10FDF /* Check.png in Resources */,
35B038130AC5B6EB00A10FDF /* ResumeNoWaitOn.png in Resources */,
35B038140AC5B6EC00A10FDF /* ResumeNoWaitOff.png in Resources */,
3518E4D70AC6253F002ED3A2 /* PiecesWindow.nib in Resources */,
3518E5210AC62A29002ED3A2 /* PiecesBack.tiff in Resources */,
3518E5230AC62A2A002ED3A2 /* BoxBlue2.tiff in Resources */,
3518E5240AC62A2B002ED3A2 /* BoxBlue1.tiff in Resources */,
3518E52B0AC62A57002ED3A2 /* BoxWhite.tiff in Resources */,
3518E52C0AC62A57002ED3A2 /* BoxBlue3.tiff in Resources */,
3518E57B0AC632EA002ED3A2 /* BoxGreen.tiff in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -824,6 +918,11 @@
4D1838D909DEC0430047D688 /* completion.c in Sources */,
4D1838FB09DEC4380047D688 /* utils.c in Sources */,
4D1838FD09DEC4380047D688 /* transmission.c in Sources */,
4DAB87C60ABE1F730081CF7E /* xml.c in Sources */,
4DAB87C80ABE1F730081CF7E /* upnp.c in Sources */,
4DAB87CA0ABE1F730081CF7E /* natpmp.c in Sources */,
4DAB87CC0ABE1F730081CF7E /* http.c in Sources */,
3518E4A60AC620FC002ED3A2 /* PiecesWindowController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -858,6 +957,7 @@
A2A3065E0AAD24A80049E2AC /* UKFNSubscribeFileWatcher.m in Sources */,
A2A306600AAD24A80049E2AC /* UKKQueue.m in Sources */,
A2A306620AAD24A80049E2AC /* UKMainThreadProxy.m in Sources */,
3518E4FB0AC62832002ED3A2 /* PiecesWindowController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -893,6 +993,14 @@
name = MainMenu.nib;
sourceTree = "<group>";
};
3518E4D50AC6253F002ED3A2 /* PiecesWindow.nib */ = {
isa = PBXVariantGroup;
children = (
3518E4D60AC6253F002ED3A2 /* English */,
);
name = PiecesWindow.nib;
sourceTree = "<group>";
};
A200B9620A227FD0007BBB1E /* InfoWindow.nib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -26,6 +26,7 @@
.Op Fl i Ar torrent-file
.Op Fl s Ar torrent-file
.Op Fl v Ar level
.Op Fl n
.Op Fl p Ar port
.Op Fl u Ar upload-rate
.Op Fl u Ar download-rate
@ -50,6 +51,9 @@ file, and exits.
.It Fl v, -verbose Ar level
Sets debugging options. The current range is 0-2, with the highest
level producing the most output. The default is 0.
.It Fl n, Fl -nat-traversal
Attempt to use the NAT-PMP and UPnP IGD protocols to establish a port
mapping for allowing incoming peer connections.
.It Fl p, -port Ar port
Specifies an alternate port for the client to listen on. The default is
9090.

View File

@ -37,14 +37,15 @@
#define USAGE \
"Usage: %s [options] file.torrent [options]\n\n" \
"Options:\n" \
" -d, --download <int> Maximum download rate (-1 = no limit, default = -1)\n"\
" -f, --finish <shell script> Command you wish to run on completion\n" \
" -h, --help Print this help and exit\n" \
" -i, --info Print metainfo and exit\n" \
" -s, --scrape Print counts of seeders/leechers and exit\n" \
" -v, --verbose <int> Verbose level (0 to 2, default = 0)\n" \
" -n --nat-traversal Attempt NAT traversal using NAT-PMP or UPnP IGD\n" \
" -p, --port <int> Port we should listen on (default = %d)\n" \
" -s, --scrape Print counts of seeders/leechers and exit\n" \
" -u, --upload <int> Maximum upload rate (-1 = no limit, default = 20)\n" \
" -d, --download <int> Maximum download rate (-1 = no limit, default = -1)\n" \
" -f, --finish <shell script> Command you wish to run on completion\n"
" -v, --verbose <int> Verbose level (0 to 2, default = 0)\n"
static int showHelp = 0;
static int showInfo = 0;
@ -55,6 +56,7 @@ static int uploadLimit = 20;
static int downloadLimit = -1;
static char * torrentPath = NULL;
static volatile char mustDie = 0;
static int natTraversal = 0;
static char * finishCall = NULL;
@ -63,7 +65,7 @@ static void sigHandler ( int signal );
int main( int argc, char ** argv )
{
int i, error;
int i, error, nat;
tr_handle_t * h;
tr_torrent_t * tor;
tr_stat_t * s;
@ -163,6 +165,15 @@ int main( int argc, char ** argv )
tr_setBindPort( h, bindPort );
tr_setUploadLimit( h, uploadLimit );
tr_setDownloadLimit( h, downloadLimit );
if( natTraversal )
{
tr_natTraversalEnable( h );
}
else
{
tr_natTraversalDisable( h );
}
tr_torrentSetFolder( tor, "." );
tr_torrentStart( tor );
@ -200,7 +211,7 @@ int main( int argc, char ** argv )
}
memset( &string[chars], ' ', 79 - chars );
string[79] = '\0';
fprintf( stderr, "\r%s", string );
//fprintf( stderr, "\r%s", string );
if( s->error & TR_ETRACKER )
{
@ -218,14 +229,18 @@ int main( int argc, char ** argv )
}
fprintf( stderr, "\n" );
/* Try for 5 seconds to notice the tracker that we are leaving */
/* Try for 5 seconds to notify the tracker that we are leaving
and to delete any port mappings for nat traversal */
tr_torrentStop( tor );
tr_natTraversalDisable( h );
for( i = 0; i < 10; i++ )
{
s = tr_torrentStat( tor );
if( s->status & TR_STATUS_PAUSE )
nat = tr_natTraversalStatus( h );
if( s->status & TR_STATUS_PAUSE && TR_NAT_TRAVERSAL_DISABLED == nat )
{
/* The 'stopped' message was sent */
/* The 'stopped' tracker message was sent
and port mappings were deleted */
break;
}
usleep( 500000 );
@ -253,10 +268,11 @@ static int parseCommandLine( int argc, char ** argv )
{ "upload", required_argument, NULL, 'u' },
{ "download", required_argument, NULL, 'd' },
{ "finish", required_argument, NULL, 'f' },
{ "nat-traversal", no_argument, NULL, 'n' },
{ 0, 0, 0, 0} };
int c, optind = 0;
c = getopt_long( argc, argv, "hisv:p:u:d:f:", long_options, &optind );
c = getopt_long( argc, argv, "hisv:p:u:d:f:n", long_options, &optind );
if( c < 0 )
{
break;
@ -287,6 +303,9 @@ static int parseCommandLine( int argc, char ** argv )
case 'f':
finishCall = optarg;
break;
case 'n':
natTraversal = 1;
break;
default:
return 1;
}

View File

@ -358,7 +358,7 @@ cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
GIOChannel *io = NULL;
GError *err;
char *datastr;
size_t len;
int len;
gsize written;
*errstr = NULL;

View File

@ -59,5 +59,6 @@ cf_freestate(benc_val_t *state);
#define PREF_ADDSTD "add-behavior-standard"
#define PREF_ADDIPC "add-behavior-ipc"
#define PREF_MSGLEVEL "message-level"
#define PREF_NAT "use-nat-traversal"
#endif /* TG_CONF_H */

View File

@ -41,6 +41,7 @@
#define DEF_USEDOWNLIMIT FALSE
#define DEF_UPLIMIT 20
#define DEF_USEUPLIMIT TRUE
#define DEF_NAT TRUE
struct prefdata {
GList *prefwidgets;
@ -170,9 +171,11 @@ makeprefwindow(GtkWindow *parent, TrBackend *back) {
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
const unsigned int rowcount = 8;
const unsigned int rowcount = 9;
GtkWidget *table = gtk_table_new(rowcount, 2, FALSE);
GtkWidget *portnum = gtk_spin_button_new_with_range(1, 0xffff, 1);
GtkWidget *natcheck = gtk_check_button_new_with_mnemonic(
_("Use NAT _Traversal (NAT-PMP and UPnP)"));
GtkWidget *dirstr = gtk_file_chooser_button_new(
_("Choose a download directory"),
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
@ -196,6 +199,8 @@ makeprefwindow(GtkWindow *parent, TrBackend *back) {
GtkTreeModel *model;
GtkTreeIter iter;
GtkCellRenderer *rend;
gboolean boolval;
int intval;
g_free(title);
gtk_widget_set_name(wind, "TransmissionDialog");
@ -206,7 +211,7 @@ makeprefwindow(GtkWindow *parent, TrBackend *back) {
gtk_window_set_resizable(GTK_WINDOW(wind), FALSE);
data->prefwidgets = makeglist(portnum, lim[0].on, lim[0].num, lim[1].on,
lim[1].num, dirstr, addstd, addipc, NULL);
lim[1].num, dirstr, addstd, addipc, natcheck, NULL);
data->parent = parent;
data->back = back;
g_object_ref(G_OBJECT(back));
@ -253,6 +258,13 @@ makeprefwindow(GtkWindow *parent, TrBackend *back) {
gtk_table_attach_defaults(GTK_TABLE(table), portnum, 1, 2, RN(ii));
ii++;
/* NAT traversal checkbox */
intval = tr_natTraversalStatus(tr_backend_handle(back));
boolval = !TR_NAT_TRAVERSAL_IS_DISABLED( intval );
setupprefwidget(natcheck, PREF_NAT, boolval);
gtk_table_attach_defaults(GTK_TABLE(table), natcheck, 0, 2, RN(ii));
ii++;
/* create the model used by the three popup menus */
model = GTK_TREE_MODEL(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT));
gtk_list_store_append(GTK_LIST_STORE(model), &iter);
@ -340,11 +352,8 @@ clickdialog(GtkWidget *widget, int resp, gpointer gdata) {
g_free(errstr);
}
applyprefs(data->back);
/* XXX would be nice to have errno strings, are they printed to stdout? */
tr_setBindPort(tr_backend_handle(data->back),
strtol(cf_getpref(PREF_PORT), NULL, 10));
setlimit(data->back);
}
if(GTK_RESPONSE_APPLY != resp)
@ -352,7 +361,7 @@ clickdialog(GtkWidget *widget, int resp, gpointer gdata) {
}
void
setlimit(TrBackend *back) {
applyprefs(TrBackend *back) {
struct { void (*func)(tr_handle_t*, int);
const char *use; const char *num; gboolean defuse; long def; } lim[] = {
{tr_setDownloadLimit, PREF_USEDOWNLIMIT, PREF_DOWNLIMIT,
@ -361,10 +370,12 @@ setlimit(TrBackend *back) {
DEF_USEUPLIMIT, DEF_UPLIMIT},
};
const char *pref;
unsigned int ii;
int ii;
tr_handle_t *tr = tr_backend_handle(back);
gboolean boolval;
for(ii = 0; ii < ALEN(lim); ii++) {
/* set upload and download limits */
for(ii = 0; ii < (int)ALEN(lim); ii++) {
pref = cf_getpref(lim[ii].use);
if(!(NULL == pref ? lim[ii].defuse : strbool(pref)))
lim[ii].func(tr, -1);
@ -373,6 +384,18 @@ setlimit(TrBackend *back) {
lim[ii].func(tr, (NULL == pref ? lim[ii].def : strtol(pref, NULL, 10)));
}
}
/* set the listening port */
if(NULL != (pref = cf_getpref(PREF_PORT)) &&
0 < (ii = strtol(pref, NULL, 10)) && 0xffff >= ii)
tr_setBindPort(tr, ii);
/* enable/disable NAT traversal */
boolval = (NULL == (pref = cf_getpref(PREF_NAT)) ? DEF_NAT : strbool(pref));
if( boolval )
tr_natTraversalEnable(tr);
else
tr_natTraversalDisable(tr);
}
void

View File

@ -32,9 +32,9 @@
GtkWidget *
makeprefwindow(GtkWindow *parent, TrBackend *back);
/* set the upload limit based on saved prefs */
/* set various things based on saved prefs */
void
setlimit(TrBackend *back);
applyprefs(TrBackend *back);
/* show the "add a torrent" dialog */
void

View File

@ -263,7 +263,7 @@ srv_io_accept(GSource *source SHUTUP, int fd, struct sockaddr *sa SHUTUP,
static int
send_msg(struct constate *con, const char *name, benc_val_t *val) {
char *buf;
size_t used, total;
int used, total;
benc_val_t dict;
char stupid;

View File

@ -177,8 +177,6 @@ main(int argc, char **argv) {
char *err;
TrBackend *back;
benc_val_t *state;
const char *pref;
long intval;
GList *argfiles;
gboolean didinit, didlock;
@ -233,13 +231,8 @@ main(int argc, char **argv) {
back = tr_backend_new();
/* set the upload limit */
setlimit(back);
/* set the listening port */
if(NULL != (pref = cf_getpref(PREF_PORT)) &&
0 < (intval = strtol(pref, NULL, 10)) && 0xffff >= intval)
tr_setBindPort(tr_backend_handle(back), intval);
/* apply a few prefs */
applyprefs(back);
makewind(mainwind, back, state, argfiles);
@ -523,6 +516,9 @@ winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
/* try to politely stop all the torrents */
tr_backend_stop_torrents(data->back);
/* shut down nat traversal */
tr_natTraversalDisable(tr_backend_handle(data->back));
/* set things up to wait for torrents to stop */
edata = g_new0(struct exitdata, 1);
edata->cbdata = data;
@ -544,10 +540,13 @@ winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
gboolean
exitcheck(gpointer gdata) {
struct exitdata *data = gdata;
int natstat = tr_natTraversalStatus(tr_backend_handle(data->cbdata->back));
/* keep going if we still have torrents and haven't hit the exit timeout */
if(time(NULL) - data->started < TRACKER_EXIT_TIMEOUT &&
!tr_backend_torrents_stopped(data->cbdata->back)) {
/* keep going if we haven't hit the exit timeout and
we either have torrents left or nat traversal is stopping */
if( time( NULL ) - data->started < TRACKER_EXIT_TIMEOUT &&
( !tr_backend_torrents_stopped( data->cbdata->back ) &&
TR_NAT_TRAVERSAL_DISABLED != natstat ) ) {
updatemodel(data->cbdata);
return TRUE;
}

View File

@ -25,16 +25,8 @@
#include "transmission.h"
#define LIST_SIZE 20
#define OUTBUF_SIZE 100
static int tr_bencSprintf( char ** buf, size_t * used, size_t * max,
char * format, ... )
#ifdef __GNUC__
__attribute__ ((format (printf, 4, 5)))
#endif
;
int _tr_bencLoad( char * buf, size_t len, benc_val_t * val, char ** end )
int _tr_bencLoad( char * buf, int len, benc_val_t * val, char ** end )
{
char * p, * e, * foo;
@ -89,7 +81,7 @@ int _tr_bencLoad( char * buf, size_t len, benc_val_t * val, char ** end )
val->val.l.vals = malloc( LIST_SIZE * sizeof( benc_val_t ) );
cur = &buf[1];
str_expected = 1;
while( (size_t)(cur - buf) < len && cur[0] != 'e' )
while( cur - buf < len && cur[0] != 'e' )
{
if( val->val.l.count == val->val.l.alloc )
{
@ -139,7 +131,7 @@ int _tr_bencLoad( char * buf, size_t len, benc_val_t * val, char ** end )
e[0] = ':';
if( p != e || 0 > val->val.s.i ||
(size_t)(val->val.s.i) > len - ((p + 1) - buf) )
val->val.s.i > len - ((p + 1) - buf) )
{
return 1;
}
@ -240,10 +232,10 @@ benc_val_t * tr_bencDictFind( benc_val_t * val, char * key )
return NULL;
}
char * tr_bencSaveMalloc( benc_val_t * val, size_t * len )
char * tr_bencSaveMalloc( benc_val_t * val, int * len )
{
char * buf = NULL;
size_t alloc = 0;
int alloc = 0;
*len = 0;
if( tr_bencSave( val, &buf, len, &alloc ) )
@ -259,14 +251,14 @@ char * tr_bencSaveMalloc( benc_val_t * val, size_t * len )
return buf;
}
int tr_bencSave( benc_val_t * val, char ** buf, size_t * used, size_t * max )
int tr_bencSave( benc_val_t * val, char ** buf, int * used, int * max )
{
int ii;
switch( val->type )
{
case TYPE_INT:
if( tr_bencSprintf( buf, used, max, "i%"PRIu64"e", val->val.i ) )
if( tr_sprintf( buf, used, max, "i%"PRIu64"e", val->val.i ) )
{
return 1;
}
@ -277,8 +269,8 @@ int tr_bencSave( benc_val_t * val, char ** buf, size_t * used, size_t * max )
{
return 1;
}
if( tr_bencSprintf( buf, used, max, "%i:%s",
val->val.s.i, val->val.s.s ) )
if( tr_sprintf( buf, used, max, "%i:%s",
val->val.s.i, val->val.s.s ) )
{
return 1;
}
@ -286,8 +278,8 @@ int tr_bencSave( benc_val_t * val, char ** buf, size_t * used, size_t * max )
case TYPE_LIST:
case TYPE_DICT:
if( tr_bencSprintf( buf, used, max,
(TYPE_LIST == val->type ? "l" : "d") ) )
if( tr_sprintf( buf, used, max,
(TYPE_LIST == val->type ? "l" : "d") ) )
{
return 1;
}
@ -298,7 +290,7 @@ int tr_bencSave( benc_val_t * val, char ** buf, size_t * used, size_t * max )
return 1;
}
}
if( tr_bencSprintf( buf, used, max, "e" ) )
if( tr_sprintf( buf, used, max, "e" ) )
{
return 1;
}
@ -307,32 +299,3 @@ int tr_bencSave( benc_val_t * val, char ** buf, size_t * used, size_t * max )
return 0;
}
static int tr_bencSprintf( char ** buf, size_t * used, size_t * max,
char * format, ... )
{
va_list ap;
int want;
char * newbuf;
va_start( ap, format );
want = vsnprintf( NULL, 0, format, ap );
va_end(ap);
while( *used + want + 1 > *max )
{
*max += OUTBUF_SIZE;
newbuf = realloc( *buf, *max );
if( NULL == newbuf )
{
return 1;
}
*buf = newbuf;
}
va_start( ap, format );
*used += vsnprintf( *buf + *used, *max - *used, format, ap );
va_end( ap );
return 0;
}

View File

@ -52,13 +52,13 @@ typedef struct benc_val_s
} benc_val_t;
#define tr_bencLoad(b,l,v,e) _tr_bencLoad((char*)(b),(l),(v),(char**)(e))
int _tr_bencLoad( char * buf, size_t len, benc_val_t * val,
int _tr_bencLoad( char * buf, int len, benc_val_t * val,
char ** end );
void tr_bencPrint( benc_val_t * val );
void tr_bencFree( benc_val_t * val );
benc_val_t * tr_bencDictFind( benc_val_t * val, char * key );
char * tr_bencSaveMalloc( benc_val_t * val, size_t * len );
char * tr_bencSaveMalloc( benc_val_t * val, int * len );
int tr_bencSave( benc_val_t * val, char ** buf,
size_t * used, size_t * max );
int * used, int * max );
#endif

View File

@ -248,7 +248,7 @@ static int fastResumeLoadOld( tr_io_t * io, FILE * file )
if( ftell( file ) != size )
{
tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
ftell( file ), size );
(int)ftell( file ), size );
fclose( file );
return 1;
}

829
libtransmission/http.c Normal file
View File

@ -0,0 +1,829 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "transmission.h"
#define HTTP_PORT 80 /* default http port 80 */
#define HTTP_TIMEOUT 60000 /* one minute http timeout */
#define HTTP_BUFSIZE 1500 /* 1.5K buffer size increment */
#define LF "\012"
#define CR "\015"
#define SP( cc ) ( ' ' == (cc) || '\t' == (cc) )
#define NL( cc ) ( '\015' == (cc) || '\012' == (cc) )
#define NUM( cc ) ( '0' <= (cc) && '9' >= (cc) )
#define ALEN( aa ) ( (int)(sizeof( (aa) ) / sizeof( (aa)[0] ) ) )
#define SKIP( off, len, done ) \
while( (off) < (len) && (done) ) { (off)++; };
static const char *
slice( const char * data, int * len, const char * delim );
static tr_tristate_t
sendrequest( tr_http_t * http );
static tr_tristate_t
receiveresponse( tr_http_t * http );
static int
checklength( tr_http_t * http );
static int
learnlength( tr_http_t * http );
#define EXPANDBUF( bs ) &(bs).buf, &(bs).used, &(bs).size
struct buf {
char * buf;
int size;
int used;
};
struct tr_http_s {
#define HTTP_STATE_CONSTRUCT 1
#define HTTP_STATE_RESOLVE 2
#define HTTP_STATE_CONNECT 3
#define HTTP_STATE_RECEIVE 4
#define HTTP_STATE_DONE 5
#define HTTP_STATE_ERROR 6
char state;
#define HTTP_LENGTH_UNKNOWN 1
#define HTTP_LENGTH_EOF 2
#define HTTP_LENGTH_FIXED 3
#define HTTP_LENGTH_CHUNKED 4
char lengthtype;
tr_resolve_t * resolve;
char * host;
int port;
int sock;
struct buf header;
struct buf body;
uint64_t date;
/*
eof: unused
fixed: lenptr is the start of the body, lenint is the body length
chunked: lenptr is start of chunk (after length), lenint is chunk size
*/
int chunkoff;
int chunklen;
};
int
tr_httpRequestType( const char * data, int len, char ** method, char ** uri )
{
const char * words[6];
int ii, ret;
const char * end;
/* find the end of the line */
for( end = data; data + len > end; end++ )
{
if( NL( *data) )
{
break;
}
}
/* find the first three "words" in the line */
for( ii = 0; ALEN( words ) > ii && data < end; ii++ )
{
/* find the next space or non-space */
while( data < end && ( ii % 2 ? !SP( *data ) : SP( *data ) ) )
{
data++;
}
/* save the beginning of the word */
words[ii] = data;
}
/* check for missing words */
if( ALEN( words) > ii )
{
return -1;
}
/* parse HTTP version */
ret = -1;
if( 8 <= words[5] - words[4] )
{
if( 0 == tr_strncasecmp( words[4], "HTTP/1.1", 8 ) )
{
ret = 11;
}
else if( 0 == tr_strncasecmp( words[4], "HTTP/1.0", 8 ) )
{
ret = 10;
}
}
/* copy the method */
if( 0 <= ret && NULL != method )
{
*method = tr_dupstr( words[0], words[1] - words[0] );
if( NULL == *method )
{
ret = -1;
}
}
/* copy uri */
if( 0 <= ret && NULL != uri )
{
*uri = tr_dupstr( words[2], words[3] - words[2] );
if( NULL == *uri )
{
free( *method );
ret = -1;
}
}
return ret;
}
int
tr_httpResponseCode( const char * data, int len )
{
char code[4];
int ret;
/* check for the minimum legal length */
if( 12 > len ||
/* check for valid http version */
0 != tr_strncasecmp( data, "HTTP/1.", 7 ) ||
( '1' != data[7] && '0' != data[7] ) ||
/* there should be at least one space after the version */
!SP( data[8] ) )
{
return -1;
}
/* skip any extra spaces */
data += 9;
len -= 9;
while( 0 < len && SP( *data ) )
{
data++;
len--;
}
/* check for a valid three-digit code */
if( 3 > len || !NUM( data[0] ) || !NUM( data[1] ) || !NUM( data[2] ) ||
( 3 < len && NUM( data[3] ) ) )
{
return -1;
}
/* parse and return the code */
memcpy( code, data, 3 );
code[3] = '\0';
ret = strtol( code, NULL, 10 );
if( 100 > ret )
{
ret = -1;
}
return ret;
}
char *
tr_httpParse( const char * data, int len, tr_http_header_t *headers )
{
const char * body, * begin;
int ii, jj, full;
/* find the end of the http headers */
body = slice( data, &len, CR LF CR LF );
if( NULL == body )
{
body = slice( data, &len, LF LF );
if( NULL == body )
{
body = slice( data, &len, CR CR );
if( NULL == body )
{
return NULL;
}
}
}
/* return if no headers were requested */
if( NULL == headers || NULL == headers[0].name )
{
return (char*) body;
}
/* NULL out all the header's data pointers */
for( ii = 0; NULL != headers[ii].name; ii++ )
{
headers[ii].data = NULL;
headers[ii].len = 0;
}
/* skip the http request or response line */
ii = 0;
SKIP( ii, len, !NL( data[ii] ) );
SKIP( ii, len, NL( data[ii] ) );
/* find the requested headers */
while(ii < len )
{
/* skip leading spaces and find the header name */
SKIP( ii, len, SP( data[ii] ) );
begin = data + ii;
SKIP( ii, len, ':' != data[ii] && !NL( data[ii] ) );
if( ':' == data[ii] )
{
full = 1;
/* try to match the found header with one of the requested */
for( jj = 0; NULL != headers[jj].name; jj++ )
{
if( NULL == headers[jj].data )
{
full = 0;
if( 0 == tr_strncasecmp( headers[jj].name, begin,
( data + ii ) - begin ) )
{
ii++;
/* skip leading whitespace and save the header value */
SKIP( ii, len, SP( data[ii] ) );
headers[jj].data = data + ii;
SKIP( ii, len, !NL( data[ii] ) );
headers[jj].len = ( data + ii ) - headers[jj].data;
break;
}
}
}
if( full )
{
break;
}
/* skip to the end of the header */
SKIP( ii, len, !NL( data[ii] ) );
}
/* skip past the newline */
SKIP( ii, len, NL( data[ii] ) );
}
return (char*)body;
}
static const char *
slice( const char * data, int * len, const char * delim )
{
const char *body;
int dlen;
dlen = strlen( delim );
body = tr_memmem( data, *len, delim, dlen );
if( NULL != body )
{
*len = body - data;
body += dlen;
}
return body;
}
int
tr_httpParseUrl( const char * url, int len,
char ** host, int * port, char ** path )
{
const char * pathstart, * hostend;
int ii, colon, portnum;
char str[6];
if( 0 > len )
{
len = strlen( url );
}
/* check for protocol */
if( 7 > len || 0 != tr_strncasecmp( url, "http://", 7 ) )
{
tr_err( "Invalid HTTP URL" );
return 1;
}
url += 7;
len -= 7;
/* find the hostname and port */
colon = -1;
for( ii = 0; len > ii && '/' != url[ii]; ii++ )
{
if( ':' == url[ii] )
{
colon = ii;
}
}
hostend = url + ( 0 > colon ? ii : colon );
pathstart = url + ii;
/* parse the port number */
portnum = HTTP_PORT;
if( 0 <= colon )
{
colon++;
memset( str, 0, sizeof( str ) );
memcpy( str, url + colon, MIN( (int) sizeof( str) - 1, ii - colon ) );
portnum = strtol( str, NULL, 10 );
if( 0 >= portnum || 0xffff <= portnum )
{
tr_err( "Invalid port (%i)", portnum );
return 1;
}
}
if( NULL != host )
{
*host = tr_dupstr( url, hostend - url );
}
if( NULL != port )
{
*port = portnum;
}
if( NULL != path )
{
if( 0 < len - ( pathstart - url ) )
{
*path = tr_dupstr( pathstart, len - ( pathstart - url ) );
}
else
{
*path = strdup( "/" );
}
}
return 0;
}
tr_http_t *
tr_httpClient( int method, const char * host, int port, const char * fmt, ... )
{
tr_http_t * http;
va_list ap1, ap2;
char * methodstr;
http = malloc( sizeof( *http ) );
if( NULL == http )
{
return NULL;
}
memset( http, 0, sizeof( *http ) );
http->state = HTTP_STATE_CONSTRUCT;
http->lengthtype = HTTP_LENGTH_UNKNOWN;
http->host = strdup( host );
http->port = port;
http->sock = -1;
if( NULL == http->host || NULL == fmt )
{
goto err;
}
switch( method )
{
case TR_HTTP_GET:
methodstr = "GET";
break;
case TR_HTTP_POST:
methodstr = "POST";
break;
case TR_HTTP_M_POST:
methodstr = "M-POST";
break;
default:
goto err;
}
if( tr_sprintf( EXPANDBUF( http->header ), "%s ", methodstr ) )
{
goto err;
}
va_start( ap1, fmt );
va_start( ap2, fmt );
if( tr_vsprintf( EXPANDBUF( http->header ), fmt, ap1, ap2 ) )
{
va_end( ap2 );
va_end( ap1 );
goto err;
}
va_end( ap2 );
va_end( ap1 );
if( tr_sprintf( EXPANDBUF( http->header ), " HTTP/1.1" CR LF
"Host: %s" CR LF
"User-Agent: Transmission/%d.%d" CR LF
"Connection: close" CR LF,
http->host, VERSION_MAJOR, VERSION_MINOR ) )
{
goto err;
}
return http;
err:
tr_httpClose( http );
return NULL;
}
void
tr_httpAddHeader( tr_http_t * http, const char * name, const char * value )
{
if( HTTP_STATE_CONSTRUCT == http->state )
{
if( tr_sprintf( EXPANDBUF( http->header ),
"%s: %s" CR LF, name, value ) )
{
http->state = HTTP_STATE_ERROR;
}
}
else
{
assert( HTTP_STATE_ERROR == http->state );
}
}
void
tr_httpAddBody( tr_http_t * http , const char * fmt , ... )
{
va_list ap1, ap2;
if( HTTP_STATE_CONSTRUCT == http->state )
{
va_start( ap1, fmt );
va_start( ap2, fmt );
if( tr_vsprintf( EXPANDBUF( http->body ), fmt, ap1, ap2 ) )
{
http->state = HTTP_STATE_ERROR;
}
va_end( ap2 );
va_end( ap1 );
}
else
{
assert( HTTP_STATE_ERROR == http->state );
}
}
tr_tristate_t
tr_httpPulse( tr_http_t * http, const char ** data, int * len )
{
struct in_addr addr;
switch( http->state )
{
case HTTP_STATE_CONSTRUCT:
if( tr_sprintf( EXPANDBUF( http->header ), "Content-length: %i"
CR LF CR LF, http->body.used ) )
{
goto err;
}
if( !tr_netResolve( http->host, &addr ) )
{
http->sock = tr_netOpenTCP( addr, htons( http->port ) );
http->state = HTTP_STATE_CONNECT;
break;
}
http->resolve = tr_netResolveInit( http->host );
if( NULL == http->resolve )
{
goto err;
}
http->state = HTTP_STATE_RESOLVE;
/* fallthrough */
case HTTP_STATE_RESOLVE:
switch( tr_netResolvePulse( http->resolve, &addr ) )
{
case TR_WAIT:
return TR_WAIT;
case TR_ERROR:
goto err;
case TR_OK:
tr_netResolveClose( http->resolve );
http->resolve = NULL;
http->sock = tr_netOpenTCP( addr, htons( http->port ) );
http->state = HTTP_STATE_CONNECT;
}
/* fallthrough */
case HTTP_STATE_CONNECT:
switch( sendrequest( http ) )
{
case TR_WAIT:
return TR_WAIT;
case TR_ERROR:
goto err;
case TR_OK:
http->state = HTTP_STATE_RECEIVE;
}
/* fallthrough */
case HTTP_STATE_RECEIVE:
switch( receiveresponse( http ) )
{
case TR_WAIT:
return TR_WAIT;
case TR_ERROR:
goto err;
case TR_OK:
goto ok;
}
break;
case HTTP_STATE_DONE:
goto ok;
case HTTP_STATE_ERROR:
goto err;
}
return TR_WAIT;
err:
http->state = HTTP_STATE_ERROR;
return TR_ERROR;
ok:
http->state = HTTP_STATE_DONE;
if( NULL != data )
{
*data = http->header.buf;
}
if( NULL != len )
{
*len = http->header.used;
}
return TR_OK;
}
static tr_tristate_t
sendrequest( tr_http_t * http )
{
struct buf * buf;
int ret;
if( 0 == http->date )
{
http->date = tr_date();
}
if( 0 > http->sock || tr_date() > http->date + HTTP_TIMEOUT )
{
return TR_ERROR;
}
buf = ( 0 < http->header.used ? &http->header : &http->body );
while( 0 < buf->used )
{
ret = tr_netSend( http->sock, (uint8_t *) buf->buf, buf->used );
if( ret & TR_NET_CLOSE )
{
return TR_ERROR;
}
else if( ret & TR_NET_BLOCK )
{
return TR_WAIT;
}
buf->used = 0;
buf = &http->body;
}
free( http->body.buf );
http->body.buf = NULL;
http->body.size = 0;
http->date = 0;
return TR_OK;
}
static tr_tristate_t
receiveresponse( tr_http_t * http )
{
int ret, before;
void * newbuf;
if( 0 == http->date )
{
http->date = tr_date();
}
before = http->header.used;
for(;;)
{
if( http->header.size - http->header.used < HTTP_BUFSIZE )
{
newbuf = realloc( http->header.buf,
http->header.size + HTTP_BUFSIZE );
if( NULL == newbuf )
{
return TR_ERROR;
}
http->header.buf = newbuf;
http->header.size += HTTP_BUFSIZE;
}
ret = tr_netRecv( http->sock,
(uint8_t *) ( http->header.buf + http->header.used ),
http->header.size - http->header.used );
if( ret & TR_NET_CLOSE )
{
checklength( http );
return TR_OK;
}
else if( ret & TR_NET_BLOCK )
{
break;
}
else
{
http->header.used += ret;
}
}
if( before < http->header.used && checklength( http ) )
{
return TR_OK;
}
if( tr_date() > HTTP_TIMEOUT + http->date )
{
return TR_ERROR;
}
return TR_WAIT;
}
static int
checklength( tr_http_t * http )
{
char * buf;
int num, ii, len;
switch( http->lengthtype )
{
case HTTP_LENGTH_UNKNOWN:
if( learnlength( http ) )
{
return checklength( http );
}
break;
case HTTP_LENGTH_EOF:
break;
case HTTP_LENGTH_FIXED:
if( http->header.used >= http->chunkoff + http->chunklen )
{
http->header.used = http->chunkoff + http->chunklen;
return 1;
}
break;
case HTTP_LENGTH_CHUNKED:
buf = http->header.buf;
while( http->header.used > http->chunkoff + http->chunklen )
{
num = http->chunkoff + http->chunklen;
while( http->header.used > num && NL( buf[num] ) )
{
num++;
}
ii = num;
while( http->header.used > ii && !NL( buf[ii] ) )
{
ii++;
}
if( http->header.used > ii )
{
/* strtol should stop at the newline */
len = strtol( buf + num, NULL, 16 );
if( 0 == len )
{
http->header.used = http->chunkoff + http->chunklen;
return 1;
}
if( http->header.used > ii + 1 )
{
ii += ( 0 == memcmp( buf + ii, CR LF, 2 ) ? 2 : 1 );
if( http->header.used > ii )
{
memmove( buf + http->chunkoff + http->chunklen,
buf + ii, http->header.used - ii );
}
http->header.used -=
ii - ( http->chunkoff + http->chunklen );
http->chunkoff += http->chunklen;
http->chunklen = len;
}
}
}
break;
}
return 0;
}
static int
learnlength( tr_http_t * http )
{
tr_http_header_t hdr[] = {
{ "Content-Length", NULL, 0 },
/*
XXX this probably doesn't handle multiple encodings correctly
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
*/
{ "Transfer-Encoding", NULL, 0 },
{ NULL, NULL, 0 }
};
const char * body;
char * duped;
body = tr_httpParse( http->header.buf, http->header.used, hdr );
if( NULL != body )
{
if( 0 < hdr[1].len &&
0 == tr_strncasecmp( "chunked", hdr[1].data, hdr[1].len ) )
{
http->lengthtype = HTTP_LENGTH_CHUNKED;
http->chunkoff = body - http->header.buf;
http->chunklen = 0;
}
else if( 0 < hdr[0].len )
{
http->lengthtype = HTTP_LENGTH_FIXED;
http->chunkoff = body - http->header.buf;
duped = tr_dupstr( hdr[0].data, hdr[0].len );
http->chunklen = strtol( duped, NULL, 10 );
free( duped );
}
else
{
http->lengthtype = HTTP_LENGTH_EOF;
}
return 1;
}
return 0;
}
char *
tr_httpWhatsMyAddress( tr_http_t * http )
{
struct sockaddr_in sin;
socklen_t size;
char buf[INET_ADDRSTRLEN];
if( 0 > http->sock )
{
return NULL;
}
size = sizeof( sin );
if( 0 > getsockname( http->sock, (struct sockaddr *) &sin, &size ) )
{
return NULL;
}
tr_netNtop( &sin.sin_addr, buf, sizeof( buf ) );
return strdup( buf );
}
void
tr_httpClose( tr_http_t * http )
{
if( NULL != http->resolve )
{
tr_netResolveClose( http->resolve );
}
free( http->host );
if( 0 <= http->sock )
{
tr_netClose( http->sock );
}
free( http->header.buf );
free( http->body.buf );
free( http );
}

67
libtransmission/http.h Normal file
View File

@ -0,0 +1,67 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef TR_HTTP_H
#define TR_HTTP_H 1
/*
Parse an HTTP request header to find the method, uri, and version.
The version will be 11, 10, or -1 on parse error. The method and/or
uri pointers may be NULL if the caller is not interested.
*/
int tr_httpRequestType( const char * data, int len,
char ** method, char ** uri );
/* Return the HTTP status code for the response, or -1 for parse error */
int tr_httpResponseCode( const char * data, int len );
#define TR_HTTP_STATUS_OK( st ) ( 200 <= (st) && 299 >= (st) )
#define TR_HTTP_STATUS_REDIRECT( st ) ( 300 <= (st) && 399 >= (st) )
#define TR_HTTP_STATUS_FAIL( st ) ( 400 <= (st) && 599 >= (st) )
/*
Parse an HTTP request or response, locating specified headers and
the body. The length of the body will be len - ( body - data ).
*/
typedef struct { const char * name; const char * data; int len; }
tr_http_header_t;
char * tr_httpParse( const char * data, int len, tr_http_header_t *headers );
int tr_httpParseUrl( const char *, int, char **, int *, char ** );
/* fetch a file via HTTP from a standard http:// url */
typedef struct tr_http_s tr_http_t;
#define TR_HTTP_GET 1
#define TR_HTTP_POST 2
#define TR_HTTP_M_POST 3
tr_http_t * tr_httpClient( int, const char *, int, const char *, ... )
PRINTF( 4, 5 );
/* only add headers or body before first pulse */
void tr_httpAddHeader( tr_http_t *, const char *, const char * );
void tr_httpAddBody( tr_http_t *, const char *, ... ) PRINTF( 2, 3 );
tr_tristate_t tr_httpPulse( tr_http_t *, const char **, int * );
char * tr_httpWhatsMyAddress( tr_http_t * );
void tr_httpClose( tr_http_t * );
#endif

View File

@ -416,7 +416,7 @@ static int readOrWriteBytes( tr_io_t * io, uint64_t offset, int size,
if( i >= inf->fileCount )
{
/* Should not happen */
tr_err( "readOrWriteBytes: offset out of range (%lld, %d, %d)",
tr_err( "readOrWriteBytes: offset out of range (%"PRIu64", %d, %d)",
offset, size, isWrite );
goto fail;
}

View File

@ -28,6 +28,9 @@
/* Standard headers used here and there.
That is probably ugly to put them all here, but it is sooo
convenient */
#if ( defined( __unix__ ) || defined( unix ) ) && !defined( USG )
#include <sys/param.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
@ -50,14 +53,21 @@
#include <assert.h>
#ifdef BEOS_NETSERVER
# define in_port_t uint16_t
# define socklen_t uint32_t
#else
# include <arpa/inet.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
#ifdef __GNUC__
# define UNUSED __attribute__((unused))
# define PRINTF( fmt, args ) __attribute__((format (printf, fmt, args)))
#else
# define UNUSED
# define PRINTF( fmt, args )
#endif
/* We use OpenSSL whenever possible, since it is likely to be more
@ -107,6 +117,8 @@ static inline void tr_htonl( uint32_t a, uint8_t * p )
typedef struct tr_completion_s tr_completion_t;
typedef enum { TR_OK, TR_ERROR, TR_WAIT } tr_tristate_t;
#include "platform.h"
#include "bencode.h"
#include "metainfo.h"
@ -118,6 +130,10 @@ typedef struct tr_completion_s tr_completion_t;
#include "ratecontrol.h"
#include "clients.h"
#include "choking.h"
#include "natpmp.h"
#include "upnp.h"
#include "http.h"
#include "xml.h"
struct tr_torrent_s
{
@ -187,6 +203,8 @@ struct tr_handle_s
tr_ratecontrol_t * download;
tr_fd_t * fdlimit;
tr_choking_t * choking;
tr_natpmp_t * natpmp;
tr_upnp_t * upnp;
int bindPort;
int bindSocket;

View File

@ -69,7 +69,7 @@ int tr_metainfoParse( tr_info_t * inf, const char * path,
}
if( sb.st_size > TORRENT_MAX_SIZE )
{
tr_err( "Torrent file is too big (%d bytes)", sb.st_size );
tr_err( "Torrent file is too big (%d bytes)", (int)sb.st_size );
return 1;
}

793
libtransmission/natpmp.c Normal file
View File

@ -0,0 +1,793 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "transmission.h"
#define PMP_PORT 5351
#define PMP_MCAST_ADDR "224.0.0.1"
#define PMP_INITIAL_DELAY 250 /* ms, 1/4 second */
#define PMP_TOTAL_DELAY 120000 /* ms, 2 minutes */
#define PMP_VERSION 0
#define PMP_OPCODE_GETIP 0
#define PMP_OPCODE_ADDUDP 1
#define PMP_OPCODE_ADDTCP 2
#define PMP_LIFETIME 3600 /* secs, one hour */
#define PMP_RESPCODE_OK 0
#define PMP_RESPCODE_BADVERS 1
#define PMP_RESPCODE_REFUSED 2
#define PMP_RESPCODE_NETDOWN 3
#define PMP_RESPCODE_NOMEM 4
#define PMP_RESPCODE_BADOPCODE 5
#define PMP_OPCODE_FROM_RESPONSE( op ) ( 0x80 ^ (op) )
#define PMP_OPCODE_TO_RESPONSE( op ) ( 0x80 | (op) )
#define PMP_OPCODE_IS_RESPONSE( op ) ( 0x80 & (op) )
#define PMP_TOBUF16( buf, num ) ( *( (uint16_t *) (buf) ) = htons( (num) ) )
#define PMP_TOBUF32( buf, num ) ( *( (uint32_t *) (buf) ) = htonl( (num) ) )
#define PMP_FROMBUF16( buf ) ( htons( *( (uint16_t *) (buf) ) ) )
#define PMP_FROMBUF32( buf ) ( htonl( *( (uint32_t *) (buf) ) ) )
typedef struct tr_natpmp_uptime_s
{
time_t when;
uint32_t uptime;
} tr_natpmp_uptime_t;
typedef struct tr_natpmp_req_s
{
unsigned int adding : 1;
unsigned int nobodyhome : 1;
unsigned int tmpfail : 1;
int fd;
int delay;
uint64_t retry;
uint64_t timeout;
int port;
tr_fd_t * fdlimit;
tr_natpmp_uptime_t * uptime;
} tr_natpmp_req_t;
struct tr_natpmp_s
{
#define PMP_STATE_IDLE 1
#define PMP_STATE_ADDING 2
#define PMP_STATE_DELETING 3
#define PMP_STATE_MAPPED 4
#define PMP_STATE_FAILED 5
#define PMP_STATE_NOBODYHOME 6
#define PMP_STATE_TMPFAIL 7
char state;
unsigned int active : 1;
struct in_addr dest;
int newport;
int mappedport;
tr_fd_t * fdlimit;
tr_lock_t lock;
uint64_t renew;
tr_natpmp_req_t * req;
tr_natpmp_uptime_t uptime;
int mcastfd;
};
static int
checktime( tr_natpmp_uptime_t * uptime, uint32_t seen );
static void
killsock( int * fd, tr_fd_t * fdlimit );
static tr_natpmp_req_t *
newreq( int adding, struct in_addr addr, int port, tr_fd_t * fdlimit,
tr_natpmp_uptime_t * uptime );
static tr_tristate_t
pulsereq( tr_natpmp_req_t * req, uint64_t * renew );
static int
mcastsetup( tr_fd_t * fdlimit );
static void
mcastpulse( tr_natpmp_t * pmp );
static void
killreq( tr_natpmp_req_t ** req );
static int
sendrequest( int adding, int fd, int port );
static tr_tristate_t
readrequest( uint8_t * buf, int len, int adding, int port,
tr_natpmp_uptime_t * uptime, uint64_t * renew, int * tmpfail );
tr_natpmp_t *
tr_natpmpInit( tr_fd_t * fdlimit )
{
tr_natpmp_t * pmp;
pmp = calloc( 1, sizeof( *pmp ) );
if( NULL == pmp )
{
return NULL;
}
pmp->state = PMP_STATE_IDLE;
pmp->fdlimit = fdlimit;
pmp->mcastfd = -1;
if( tr_getDefaultRoute( &pmp->dest ) || INADDR_ANY == pmp->dest.s_addr )
{
pmp->dest.s_addr = INADDR_NONE;
}
if( INADDR_NONE == pmp->dest.s_addr )
{
tr_dbg( "nat-pmp device is unknown" );
}
else
{
char addrstr[INET_ADDRSTRLEN];
tr_netNtop( &pmp->dest, addrstr, sizeof( addrstr ) );
tr_dbg( "nat-pmp device is %s", addrstr );
}
tr_lockInit( &pmp->lock );
return pmp;
}
void
tr_natpmpStart( tr_natpmp_t * pmp )
{
tr_lockLock( &pmp->lock );
if( !pmp->active )
{
tr_inf( "starting nat-pmp" );
pmp->active = 1;
if( 0 > pmp->mcastfd )
{
pmp->mcastfd = mcastsetup( pmp->fdlimit );
}
/* XXX should I change state? */
}
tr_lockUnlock( &pmp->lock );
}
void
tr_natpmpStop( tr_natpmp_t * pmp )
{
tr_lockLock( &pmp->lock );
if( pmp->active )
{
tr_inf( "stopping nat-pmp" );
pmp->active = 0;
killsock( &pmp->mcastfd, pmp->fdlimit );
switch( pmp->state )
{
case PMP_STATE_IDLE:
break;
case PMP_STATE_ADDING:
pmp->state = PMP_STATE_IDLE;
tr_dbg( "nat-pmp state add -> idle" );
if( NULL != pmp->req )
{
killreq( &pmp->req );
pmp->mappedport = pmp->req->port;
pmp->state = PMP_STATE_DELETING;
tr_dbg( "nat-pmp state idle -> del" );
}
break;
case PMP_STATE_DELETING:
break;
case PMP_STATE_MAPPED:
pmp->state = PMP_STATE_DELETING;
tr_dbg( "nat-pmp state mapped -> del" );
break;
case PMP_STATE_FAILED:
case PMP_STATE_NOBODYHOME:
case PMP_STATE_TMPFAIL:
break;
default:
assert( 0 );
break;
}
}
tr_lockUnlock( &pmp->lock );
}
int
tr_natpmpStatus( tr_natpmp_t * pmp )
{
int ret;
tr_lockLock( &pmp->lock );
if( !pmp->active )
{
ret = ( PMP_STATE_DELETING == pmp->state ?
TR_NAT_TRAVERSAL_UNMAPPING : TR_NAT_TRAVERSAL_DISABLED );
}
else if( 0 < pmp->mappedport )
{
ret = TR_NAT_TRAVERSAL_MAPPED;
}
else
{
switch( pmp->state )
{
case PMP_STATE_IDLE:
case PMP_STATE_ADDING:
case PMP_STATE_DELETING:
ret = TR_NAT_TRAVERSAL_MAPPING;
break;
case PMP_STATE_FAILED:
case PMP_STATE_TMPFAIL:
ret = TR_NAT_TRAVERSAL_ERROR;
break;
case PMP_STATE_NOBODYHOME:
ret = TR_NAT_TRAVERSAL_NOTFOUND;
break;
case PMP_STATE_MAPPED:
default:
assert( 0 );
ret = TR_NAT_TRAVERSAL_ERROR;
break;
}
}
tr_lockUnlock( &pmp->lock );
return ret;
}
void
tr_natpmpForwardPort( tr_natpmp_t * pmp, int port )
{
tr_lockLock( &pmp->lock );
tr_inf( "nat-pmp set port %i", port );
pmp->newport = port;
tr_lockUnlock( &pmp->lock );
}
void
tr_natpmpClose( tr_natpmp_t * pmp )
{
/* try to send at least one delete request if we have a port mapping */
tr_natpmpStop( pmp );
tr_natpmpPulse( pmp );
tr_lockLock( &pmp->lock );
killreq( &pmp->req );
tr_lockClose( &pmp->lock );
free( pmp );
}
void
tr_natpmpPulse( tr_natpmp_t * pmp )
{
tr_lockLock( &pmp->lock );
if( 0 <= pmp->mcastfd )
{
mcastpulse( pmp );
}
if( pmp->active || PMP_STATE_DELETING == pmp->state )
{
switch( pmp->state )
{
case PMP_STATE_IDLE:
case PMP_STATE_TMPFAIL:
if( 0 < pmp->newport )
{
tr_dbg( "nat-pmp state %s -> add with port %i",
( PMP_STATE_IDLE == pmp->state ? "idle" : "err" ),
pmp->newport );
pmp->state = PMP_STATE_ADDING;
}
break;
case PMP_STATE_ADDING:
if( NULL == pmp->req )
{
if( 0 >= pmp->newport )
{
tr_dbg( "nat-pmp state add -> idle, no port" );
pmp->state = PMP_STATE_IDLE;
}
else if( INADDR_NONE == pmp->dest.s_addr )
{
tr_dbg( "nat-pmp state add -> fail, no default route" );
pmp->state = PMP_STATE_FAILED;
}
else
{
pmp->req = newreq( 1, pmp->dest, pmp->newport,
pmp->fdlimit, &pmp->uptime );
if( NULL == pmp->req )
{
pmp->state = PMP_STATE_FAILED;
tr_dbg( "nat-pmp state add -> fail on req init" );
}
}
}
if( PMP_STATE_ADDING == pmp->state )
{
switch( pulsereq( pmp->req, &pmp->renew ) )
{
case TR_ERROR:
if( pmp->req->nobodyhome )
{
pmp->state = PMP_STATE_NOBODYHOME;
tr_dbg( "nat-pmp state add -> nobodyhome on pulse" );
}
else if( pmp->req->tmpfail )
{
pmp->state = PMP_STATE_TMPFAIL;
tr_dbg( "nat-pmp state add -> err on pulse" );
if( pmp->req->port == pmp->newport )
{
pmp->newport = 0;
}
}
else
{
pmp->state = PMP_STATE_FAILED;
tr_dbg( "nat-pmp state add -> fail on pulse" );
}
killreq( &pmp->req );
break;
case TR_OK:
pmp->mappedport = pmp->req->port;
killreq( &pmp->req );
pmp->state = PMP_STATE_MAPPED;
tr_dbg( "nat-pmp state add -> mapped with port %i",
pmp->mappedport);
tr_inf( "nat-pmp mapped port %i", pmp->mappedport );
break;
case TR_WAIT:
break;
}
}
break;
case PMP_STATE_DELETING:
if( NULL == pmp->req )
{
assert( 0 < pmp->mappedport );
pmp->req = newreq( 0, pmp->dest, pmp->mappedport,
pmp->fdlimit, &pmp->uptime );
if( NULL == pmp->req )
{
pmp->state = PMP_STATE_FAILED;
tr_dbg( "nat-pmp state del -> fail on req init" );
}
}
if( PMP_STATE_DELETING == pmp->state )
{
switch( pulsereq( pmp->req, &pmp->renew ) )
{
case TR_ERROR:
if( pmp->req->nobodyhome )
{
pmp->state = PMP_STATE_NOBODYHOME;
tr_dbg( "nat-pmp state del -> nobodyhome on pulse" );
}
else if( pmp->req->tmpfail )
{
pmp->state = PMP_STATE_TMPFAIL;
tr_dbg( "nat-pmp state del -> err on pulse" );
pmp->mappedport = -1;
}
else
{
pmp->state = PMP_STATE_FAILED;
tr_dbg( "nat-pmp state del -> fail on pulse" );
}
killreq( &pmp->req );
break;
case TR_OK:
tr_dbg( "nat-pmp state del -> idle with port %i",
pmp->req->port);
tr_inf( "nat-pmp unmapped port %i", pmp->req->port );
pmp->mappedport = -1;
killreq( &pmp->req );
pmp->state = PMP_STATE_IDLE;
break;
case TR_WAIT:
break;
}
}
break;
case PMP_STATE_MAPPED:
if( pmp->newport != pmp->mappedport )
{
tr_dbg( "nat-pmp state mapped -> del, port from %i to %i",
pmp->mappedport, pmp->newport );
pmp->state = PMP_STATE_DELETING;
}
else if( tr_date() > pmp->renew )
{
pmp->state = PMP_STATE_ADDING;
tr_dbg( "nat-pmp state mapped -> add for renewal" );
}
break;
case PMP_STATE_FAILED:
case PMP_STATE_NOBODYHOME:
break;
default:
assert( 0 );
break;
}
}
tr_lockUnlock( &pmp->lock );
}
static int
checktime( tr_natpmp_uptime_t * uptime, uint32_t cursecs )
{
time_t now;
int ret;
uint32_t estimated;
now = time( NULL );
ret = 0;
if( 0 < uptime->when )
{
estimated = ( ( now - uptime->when ) * 7 / 8 ) + uptime->uptime;
if( estimated > cursecs )
{
ret = 1;
}
}
uptime->when = now;
uptime->uptime = cursecs;
return ret;
}
static void
killsock( int * fd, tr_fd_t * fdlimit )
{
if( 0 <= *fd )
{
tr_netClose( *fd );
*fd = -1;
tr_fdSocketClosed( fdlimit, 0 );
}
}
static tr_natpmp_req_t *
newreq( int adding, struct in_addr addr, int port, tr_fd_t * fdlimit,
tr_natpmp_uptime_t * uptime )
{
tr_natpmp_req_t * ret;
uint64_t now;
ret = calloc( 1, sizeof( *ret ) );
if( NULL == ret )
{
goto err;
}
ret->fd = -1;
if( tr_fdSocketWillCreate( fdlimit, 0 ) )
{
goto err;
}
ret->fd = tr_netOpenUDP( addr, htons( PMP_PORT ) );
if( 0 > ret->fd )
{
goto err;
}
if( sendrequest( adding, ret->fd, port ) )
{
goto err;
}
now = tr_date();
ret->adding = adding;
ret->delay = PMP_INITIAL_DELAY;
ret->retry = now + PMP_INITIAL_DELAY;
ret->timeout = now + PMP_TOTAL_DELAY;
ret->port = port;
ret->fdlimit = fdlimit;
ret->uptime = uptime;
return ret;
err:
if( NULL != ret )
{
killsock( &ret->fd, fdlimit );
}
free( ret );
return NULL;
}
static tr_tristate_t
pulsereq( tr_natpmp_req_t * req, uint64_t * renew )
{
struct sockaddr_in sin;
uint8_t buf[16];
int res, tmpfail;
uint64_t now;
tr_tristate_t ret;
now = tr_date();
if( now >= req->timeout )
{
tr_dbg( "nat-pmp request timed out" );
req->nobodyhome = 1;
return TR_ERROR;
}
if( now >= req->retry )
{
if( sendrequest( req->adding, req->fd, req->port ) )
{
return TR_ERROR;
}
req->delay *= 2;
req->timeout = now + req->delay;
}
res = tr_netRecvFrom( req->fd, buf, sizeof( buf ), &sin );
if( TR_NET_BLOCK & res )
{
return TR_WAIT;
}
else if( TR_NET_CLOSE & res )
{
if( ECONNRESET == errno || ECONNREFUSED == errno )
{
tr_dbg( "nat-pmp not supported by device" );
req->nobodyhome = 1;
}
else
{
tr_inf( "error reading nat-pmp response (%s)", strerror( errno ) );
}
return TR_ERROR;
}
tr_dbg( "nat-pmp read %i byte response", res );
ret = readrequest( buf, res, req->adding, req->port, req->uptime, renew,
&tmpfail );
req->tmpfail = ( tmpfail ? 1 : 0 );
return ret;
}
static int
mcastsetup( tr_fd_t * fdlimit )
{
int fd;
struct in_addr addr;
if( tr_fdSocketWillCreate( fdlimit, 0 ) )
{
return -1;
}
addr.s_addr = inet_addr( PMP_MCAST_ADDR );
fd = tr_netMcastOpen( PMP_PORT, addr );
if( 0 > fd )
{
tr_fdSocketClosed( fdlimit, 0 );
return -1;
}
tr_dbg( "nat-pmp create multicast socket %i", fd );
return fd;
}
static void
mcastpulse( tr_natpmp_t * pmp )
{
struct sockaddr_in sin;
uint8_t buf[16];
int res;
char dbgstr[INET_ADDRSTRLEN];
res = tr_netRecvFrom( pmp->mcastfd, buf, sizeof( buf ), &sin );
if( TR_NET_BLOCK & res )
{
return;
}
else if( TR_NET_CLOSE & res )
{
tr_err( "error reading nat-pmp multicast message" );
killsock( &pmp->mcastfd, pmp->fdlimit );
return;
}
tr_netNtop( &sin.sin_addr, dbgstr, sizeof( dbgstr ) );
tr_dbg( "nat-pmp read %i byte multicast packet from %s", res, dbgstr );
if( pmp->dest.s_addr != sin.sin_addr.s_addr )
{
tr_dbg( "nat-pmp ignoring multicast packet from unknown host %s",
dbgstr );
return;
}
if( TR_OK == readrequest( buf, res, 0, -1, &pmp->uptime, &pmp->renew, NULL ) &&
PMP_STATE_NOBODYHOME == pmp->state )
{
tr_dbg( "nat-pmp state notfound -> idle" );
pmp->state = PMP_STATE_IDLE;
}
}
static void
killreq( tr_natpmp_req_t ** req )
{
if( NULL != *req )
{
killsock( &(*req)->fd, (*req)->fdlimit );
free( *req );
*req = NULL;
}
}
static int
sendrequest( int adding, int fd, int port )
{
uint8_t buf[12];
int res;
buf[0] = PMP_VERSION;
buf[1] = PMP_OPCODE_ADDTCP;
buf[2] = 0;
buf[3] = 0;
PMP_TOBUF16( buf + 4, port );
if( adding )
{
PMP_TOBUF16( buf + 6, port );
PMP_TOBUF32( buf + 8, PMP_LIFETIME );
}
else
{
PMP_TOBUF16( buf + 6, 0 );
PMP_TOBUF32( buf + 8, 0 );
}
res = tr_netSend( fd, buf, sizeof( buf ) );
/* XXX is it all right to assume the entire thing is written? */
/* XXX I should handle blocking here */
return ( ( TR_NET_CLOSE | TR_NET_BLOCK ) & res ? 1 : 0 );
}
static tr_tristate_t
readrequest( uint8_t * buf, int len, int adding, int port,
tr_natpmp_uptime_t * uptime, uint64_t * renew, int * tmpfail )
{
uint8_t version, opcode, wantedopcode;
uint16_t rescode, privport, pubport;
uint32_t seconds, lifetime;
assert( !adding || NULL != tmpfail );
if( NULL != tmpfail )
{
*tmpfail = 0;
}
if( 4 > len )
{
tr_err( "read truncated %i byte nat-pmp response packet", len );
return TR_ERROR;
}
version = buf[0];
opcode = buf[1];
rescode = PMP_FROMBUF16( buf + 2 );
wantedopcode = ( 0 < port ? PMP_OPCODE_ADDTCP : PMP_OPCODE_GETIP );
if( !PMP_OPCODE_IS_RESPONSE( opcode ) )
{
tr_dbg( "nat-pmp ignoring request packet" );
return TR_WAIT;
}
opcode = PMP_OPCODE_FROM_RESPONSE( opcode );
if( PMP_VERSION != version )
{
tr_err( "bad nat-pmp version %hhu", buf[0] );
return TR_ERROR;
}
if( wantedopcode != opcode )
{
tr_err( "bad nat-pmp opcode %hhu", opcode );
return TR_ERROR;
}
switch( rescode )
{
case PMP_RESPCODE_OK:
break;
case PMP_RESPCODE_REFUSED:
case PMP_RESPCODE_NETDOWN:
case PMP_RESPCODE_NOMEM:
if( NULL != tmpfail )
{
*tmpfail = 1;
}
/* fallthrough */
default:
tr_err( "bad nat-pmp result code %hu", rescode );
return TR_ERROR;
}
if( 8 > len )
{
tr_err( "read truncated %i byte nat-pmp response packet", len );
return TR_ERROR;
}
seconds = PMP_FROMBUF32( buf + 4 );
if( checktime( uptime, seconds ) )
{
*renew = 0;
tr_inf( "detected nat-pmp device reset" );
/* XXX should reset retry counter here */
return TR_WAIT;
}
if( 0 <= port )
{
assert( PMP_OPCODE_ADDTCP == wantedopcode );
if( 16 > len )
{
tr_err( "read truncated %i byte nat-pmp response packet", len );
return TR_ERROR;
}
privport = PMP_FROMBUF16( buf + 8 );
pubport = PMP_FROMBUF16( buf + 10 );
lifetime = PMP_FROMBUF32( buf + 12 );
if( port != privport )
{
/* private port doesn't match, ignore it */
tr_dbg( "nat-pmp ignoring message for port %i, expected port %i",
privport, port );
return TR_WAIT;
}
if( adding )
{
if( port != pubport )
{
*tmpfail = 1;
/* XXX should just start announcing the pub port we're given */
return TR_ERROR;
}
tr_dbg( "nat-pmp set renew to half of %u", lifetime );
*renew = tr_date() + ( lifetime / 2 * 1000 );
}
}
return TR_OK;
}

38
libtransmission/natpmp.h Normal file
View File

@ -0,0 +1,38 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef TR_NATPMP_H
#define TR_NATPMP_H 1
typedef struct tr_natpmp_s tr_natpmp_t;
tr_natpmp_t * tr_natpmpInit( tr_fd_t * );
void tr_natpmpStart( tr_natpmp_t * );
void tr_natpmpStop( tr_natpmp_t * );
int tr_natpmpStatus( tr_natpmp_t * );
void tr_natpmpForwardPort( tr_natpmp_t *, int );
void tr_natpmpPulse( tr_natpmp_t * );
void tr_natpmpClose( tr_natpmp_t * );
#endif

View File

@ -35,7 +35,7 @@
* representing numbers expressed in the Internet standard `.' notation.
* Returns a non-zero value if an error occurs.
**********************************************************************/
int tr_netResolve( char * address, struct in_addr * addr )
int tr_netResolve( const char * address, struct in_addr * addr )
{
addr->s_addr = inet_addr( address );
return ( addr->s_addr == 0xFFFFFFFF );
@ -52,7 +52,7 @@ static void resolveFunc ( void * );
struct tr_resolve_s
{
int status;
tr_tristate_t status;
char * address;
struct in_addr addr;
@ -94,12 +94,12 @@ void tr_netResolveThreadClose()
***********************************************************************
* Adds an address to the resolution queue.
**********************************************************************/
tr_resolve_t * tr_netResolveInit( char * address )
tr_resolve_t * tr_netResolveInit( const char * address )
{
tr_resolve_t * r;
r = malloc( sizeof( tr_resolve_t ) );
r->status = TR_RESOLVE_WAIT;
r->status = TR_WAIT;
r->address = strdup( address );
r->refcount = 2;
r->next = NULL;
@ -125,13 +125,13 @@ tr_resolve_t * tr_netResolveInit( char * address )
***********************************************************************
* Checks the current status of a resolution.
**********************************************************************/
int tr_netResolvePulse( tr_resolve_t * r, struct in_addr * addr )
tr_tristate_t tr_netResolvePulse( tr_resolve_t * r, struct in_addr * addr )
{
int ret;
tr_tristate_t ret;
tr_lockLock( &resolveLock );
ret = r->status;
if( ret == TR_RESOLVE_OK )
if( ret == TR_OK )
{
*addr = r->addr;
}
@ -201,11 +201,11 @@ static void resolveFunc( void * arg UNUSED )
if( host )
{
memcpy( &r->addr, host->h_addr, host->h_length );
r->status = TR_RESOLVE_OK;
r->status = TR_OK;
}
else
{
r->status = TR_RESOLVE_ERROR;
r->status = TR_ERROR;
}
resolveQueue = r->next;
@ -251,11 +251,11 @@ static int makeSocketNonBlocking( int s )
return s;
}
static int createSocket()
static int createSocket( int type )
{
int s;
s = socket( AF_INET, SOCK_STREAM, 0 );
s = socket( AF_INET, type, 0 );
if( s < 0 )
{
tr_err( "Could not create socket (%s)", strerror( errno ) );
@ -265,12 +265,12 @@ static int createSocket()
return makeSocketNonBlocking( s );
}
int tr_netOpen( struct in_addr addr, in_port_t port )
int tr_netOpen( struct in_addr addr, in_port_t port, int type )
{
int s;
struct sockaddr_in sock;
s = createSocket();
s = createSocket( type );
if( s < 0 )
{
return -1;
@ -293,15 +293,46 @@ int tr_netOpen( struct in_addr addr, in_port_t port )
return s;
}
int tr_netBind( int port )
#ifdef IP_ADD_MEMBERSHIP
int tr_netMcastOpen( int port, struct in_addr addr )
{
int fd;
struct ip_mreq req;
fd = tr_netBindUDP( port );
if( 0 > fd )
{
return -1;
}
memset( &req, 0, sizeof( req ) );
req.imr_multiaddr.s_addr = addr.s_addr;
req.imr_interface.s_addr = htonl( INADDR_ANY );
if( setsockopt( fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof ( req ) ) )
{
tr_err( "Could not join multicast group (%s)", strerror( errno ) );
tr_netClose( fd );
return -1;
}
return fd;
}
#else /* IP_ADD_MEMBERSHIP */
int tr_netMcastOpen( int port UNUSED, struct in_addr addr UNUSED )
{
return -1;
}
#endif /* IP_ADD_MEMBERSHIP */
int tr_netBind( int port, int type )
{
int s;
struct sockaddr_in sock;
#ifdef SO_REUSEADDR
#if defined( SO_REUSEADDR ) || defined( SO_REUSEPORT )
int optval;
#endif
s = createSocket();
s = createSocket( type );
if( s < 0 )
{
return -1;
@ -312,6 +343,14 @@ int tr_netBind( int port )
setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof( optval ) );
#endif
#ifdef SO_REUSEPORT
if( SOCK_DGRAM == type )
{
optval = 1;
setsockopt( s, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof( optval ) );
}
#endif
memset( &sock, 0, sizeof( sock ) );
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = INADDR_ANY;
@ -324,9 +363,6 @@ int tr_netBind( int port )
tr_netClose( s );
return -1;
}
tr_inf( "Binded port %d", port );
listen( s, 5 );
return s;
}
@ -371,11 +407,13 @@ int tr_netSend( int s, uint8_t * buf, int size )
return ret;
}
int tr_netRecv( int s, uint8_t * buf, int size )
int tr_netRecvFrom( int s, uint8_t * buf, int size, struct sockaddr_in * addr )
{
int ret;
socklen_t len;
int ret;
ret = recv( s, buf, size, 0 );
len = ( NULL == addr ? 0 : sizeof( *addr ) );
ret = recvfrom( s, buf, size, 0, ( struct sockaddr * ) addr, &len );
if( ret < 0 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )

View File

@ -26,30 +26,33 @@
/***********************************************************************
* DNS resolution
**********************************************************************/
int tr_netResolve( char *, struct in_addr * );
int tr_netResolve( const char *, struct in_addr * );
#define TR_RESOLVE_WAIT 0
#define TR_RESOLVE_ERROR 1
#define TR_RESOLVE_OK 2
typedef struct tr_resolve_s tr_resolve_t;
void tr_netResolveThreadInit();
void tr_netResolveThreadClose();
tr_resolve_t * tr_netResolveInit( char * );
int tr_netResolvePulse( tr_resolve_t *, struct in_addr * );
tr_resolve_t * tr_netResolveInit( const char * );
tr_tristate_t tr_netResolvePulse( tr_resolve_t *, struct in_addr * );
void tr_netResolveClose( tr_resolve_t * );
/***********************************************************************
* TCP sockets
**********************************************************************/
int tr_netOpen ( struct in_addr addr, in_port_t port );
int tr_netBind ( int );
#define tr_netOpenTCP( addr, port ) tr_netOpen( (addr), (port), SOCK_STREAM )
#define tr_netOpenUDP( addr, port ) tr_netOpen( (addr), (port), SOCK_DGRAM )
int tr_netOpen ( struct in_addr addr, in_port_t port, int type );
int tr_netMcastOpen( int port, struct in_addr addr );
#define tr_netBindTCP( port ) tr_netBind( (port), SOCK_STREAM )
#define tr_netBindUDP( port ) tr_netBind( (port), SOCK_DGRAM )
int tr_netBind ( int port, int type );
int tr_netAccept ( int s, struct in_addr *, in_port_t * );
void tr_netClose ( int s );
#define TR_NET_BLOCK 0x80000000
#define TR_NET_CLOSE 0x40000000
int tr_netSend ( int s, uint8_t * buf, int size );
int tr_netRecv ( int s, uint8_t * buf, int size );
#define tr_netRecv( s, buf, size ) tr_netRecvFrom( (s), (buf), (size), NULL )
int tr_netRecvFrom( int s, uint8_t * buf, int size, struct sockaddr_in * );
void tr_netNtop( const struct in_addr * addr, char * buf, int len );

View File

@ -100,6 +100,8 @@ struct tr_peer_s
};
#define peer_dbg( a... ) __peer_dbg( peer, ## a )
static void __peer_dbg( tr_peer_t * peer, char * msg, ... ) PRINTF( 2, 3 );
static void __peer_dbg( tr_peer_t * peer, char * msg, ... )
{
char string[256];

View File

@ -151,7 +151,7 @@ static int checkPeer( tr_torrent_t * tor, int i )
if( ( peer->status & PEER_STATUS_IDLE ) &&
!tr_fdSocketWillCreate( tor->fdlimit, 0 ) )
{
peer->socket = tr_netOpen( peer->addr, peer->port );
peer->socket = tr_netOpenTCP( peer->addr, peer->port );
if( peer->socket < 0 )
{
peer_dbg( "connection failed" );

View File

@ -196,3 +196,371 @@ void tr_lockClose( tr_lock_t * l )
#endif
}
#if defined( BSD )
#include <sys/sysctl.h>
#include <net/route.h>
static uint8_t *
getroute( int * buflen );
static int
parseroutes( uint8_t * buf, int len, struct in_addr * addr );
int
tr_getDefaultRoute( struct in_addr * addr )
{
uint8_t * buf;
int len;
buf = getroute( &len );
if( NULL == buf )
{
tr_err( "failed to get default route (BSD)" );
return 1;
}
len = parseroutes( buf, len, addr );
free( buf );
return len;
}
#ifndef SA_SIZE
#define ROUNDUP( a, size ) \
( ( (a) & ( (size) - 1 ) ) ? ( 1 + ( (a) | ( (size) - 1 ) ) ) : (a) )
#define SA_SIZE( sap ) \
( sap->sa_len ? ROUNDUP( (sap)->sa_len, sizeof( u_long ) ) : \
sizeof( u_long ) )
#endif /* !SA_SIZE */
#define NEXT_SA( sap ) \
(struct sockaddr *) ( (caddr_t) (sap) + ( SA_SIZE( (sap) ) ) )
static uint8_t *
getroute( int * buflen )
{
int mib[6];
size_t len;
uint8_t * buf;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
mib[5] = RTF_GATEWAY;
if( sysctl( mib, 6, NULL, &len, NULL, 0 ) )
{
if( ENOENT != errno )
{
tr_err( "sysctl net.route.0.inet.flags.gateway failed (%s)",
strerror( errno ) );
}
*buflen = 0;
return NULL;
}
buf = malloc( len );
if( NULL == buf )
{
*buflen = 0;
return NULL;
}
if( sysctl( mib, 6, buf, &len, NULL, 0 ) )
{
tr_err( "sysctl net.route.0.inet.flags.gateway failed (%s)",
strerror( errno ) );
free( buf );
*buflen = 0;
return NULL;
}
*buflen = len;
return buf;
}
static int
parseroutes( uint8_t * buf, int len, struct in_addr * addr )
{
uint8_t * end;
struct rt_msghdr * rtm;
struct sockaddr * sa;
struct sockaddr_in * sin;
int ii;
struct in_addr dest, gw;
end = buf + len;
while( end > buf + sizeof( *rtm ) )
{
rtm = (struct rt_msghdr *) buf;
buf += rtm->rtm_msglen;
if( end >= buf )
{
dest.s_addr = INADDR_NONE;
gw.s_addr = INADDR_NONE;
sa = (struct sockaddr *) ( rtm + 1 );
for( ii = 0; ii < RTAX_MAX && (uint8_t *) sa < buf; ii++ )
{
if( buf < (uint8_t *) NEXT_SA( sa ) )
{
break;
}
if( rtm->rtm_addrs & ( 1 << ii ) )
{
if( AF_INET == sa->sa_family )
{
sin = (struct sockaddr_in *) sa;
switch( ii )
{
case RTAX_DST:
dest = sin->sin_addr;
break;
case RTAX_GATEWAY:
gw = sin->sin_addr;
break;
}
}
sa = NEXT_SA( sa );
}
}
if( INADDR_ANY == dest.s_addr && INADDR_NONE != gw.s_addr )
{
*addr = gw;
return 0;
}
}
}
return 1;
}
#elif defined( linux ) || defined( __linux ) || defined( __linux__ )
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#define SEQNUM 195909
static int
getsock( void );
static uint8_t *
getroute( int fd, unsigned int * buflen );
static int
parseroutes( uint8_t * buf, unsigned int len, struct in_addr * addr );
int
tr_getDefaultRoute( struct in_addr * addr )
{
int fd, ret;
unsigned int len;
uint8_t * buf;
ret = 1;
fd = getsock();
if( 0 <= fd )
{
while( ret )
{
buf = getroute( fd, &len );
if( NULL == buf )
{
break;
}
ret = parseroutes( buf, len, addr );
free( buf );
}
close( fd );
}
if( ret )
{
tr_err( "failed to get default route (Linux)" );
}
return ret;
}
static int
getsock( void )
{
int fd, flags;
struct
{
struct nlmsghdr nlh;
struct rtgenmsg rtg;
} req;
struct sockaddr_nl snl;
fd = socket( PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE );
if( 0 > fd )
{
tr_err( "failed to create routing socket (%s)", strerror( errno ) );
return -1;
}
flags = fcntl( fd, F_GETFL );
if( 0 > flags || 0 > fcntl( fd, F_SETFL, O_NONBLOCK | flags ) )
{
tr_err( "failed to set socket nonblocking (%s)", strerror( errno ) );
close( fd );
return -1;
}
bzero( &snl, sizeof( snl ) );
snl.nl_family = AF_NETLINK;
bzero( &req, sizeof( req ) );
req.nlh.nlmsg_len = NLMSG_LENGTH( sizeof( req.rtg ) );
req.nlh.nlmsg_type = RTM_GETROUTE;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_seq = SEQNUM;
req.nlh.nlmsg_pid = 0;
req.rtg.rtgen_family = AF_INET;
if( 0 > sendto( fd, &req, sizeof( req ), 0,
(struct sockaddr *) &snl, sizeof( snl ) ) )
{
tr_err( "failed to write to routing socket (%s)", strerror( errno ) );
close( fd );
return -1;
}
return fd;
}
static uint8_t *
getroute( int fd, unsigned int * buflen )
{
void * buf;
unsigned int len;
ssize_t res;
struct sockaddr_nl snl;
socklen_t slen;
len = 8192;
buf = calloc( 1, len );
if( NULL == buf )
{
*buflen = 0;
return NULL;
}
for( ;; )
{
bzero( &snl, sizeof( snl ) );
slen = sizeof( snl );
res = recvfrom( fd, buf, len, 0, (struct sockaddr *) &snl, &slen );
if( 0 > res )
{
if( EAGAIN != errno )
{
tr_err( "failed to read from routing socket (%s)",
strerror( errno ) );
}
free( buf );
*buflen = 0;
return NULL;
}
if( slen < sizeof( snl ) || AF_NETLINK != snl.nl_family )
{
tr_err( "bad address" );
free( buf );
*buflen = 0;
return NULL;
}
if( 0 == snl.nl_pid )
{
break;
}
}
*buflen = res;
return buf;
}
static int
parseroutes( uint8_t * buf, unsigned int len, struct in_addr * addr )
{
struct nlmsghdr * nlm;
struct nlmsgerr * nle;
struct rtmsg * rtm;
struct rtattr * rta;
int rtalen;
struct in_addr gw, dst;
nlm = ( struct nlmsghdr * ) buf;
while( NLMSG_OK( nlm, len ) )
{
gw.s_addr = INADDR_ANY;
dst.s_addr = INADDR_ANY;
if( NLMSG_ERROR == nlm->nlmsg_type )
{
nle = (struct nlmsgerr *) NLMSG_DATA( nlm );
if( NLMSG_LENGTH( NLMSG_ALIGN( sizeof( struct nlmsgerr ) ) ) >
nlm->nlmsg_len )
{
tr_err( "truncated netlink error" );
}
else
{
tr_err( "netlink error (%s)", strerror( nle->error ) );
}
return 1;
}
else if( RTM_NEWROUTE == nlm->nlmsg_type && SEQNUM == nlm->nlmsg_seq &&
getpid() == (pid_t) nlm->nlmsg_pid &&
NLMSG_LENGTH( sizeof( struct rtmsg ) ) <= nlm->nlmsg_len )
{
rtm = NLMSG_DATA( nlm );
rta = RTM_RTA( rtm );
rtalen = RTM_PAYLOAD( nlm );
while( RTA_OK( rta, rtalen ) )
{
if( sizeof( struct in_addr ) <= RTA_PAYLOAD( rta ) )
{
switch( rta->rta_type )
{
case RTA_GATEWAY:
memcpy( &gw, RTA_DATA( rta ), sizeof( gw ) );
break;
case RTA_DST:
memcpy( &dst, RTA_DATA( rta ), sizeof( dst ) );
break;
}
}
rta = RTA_NEXT( rta, rtalen );
}
}
if( INADDR_NONE != gw.s_addr && INADDR_ANY != gw.s_addr &&
INADDR_ANY == dst.s_addr )
{
*addr = gw;
return 0;
}
nlm = NLMSG_NEXT( nlm, len );
}
return 1;
}
#else /* not BSD or Linux */
int
tr_getDefaultRoute( struct in_addr * addr UNUSED )
{
tr_inf( "don't know how to get default route on this platform" );
return 1;
}
#endif

View File

@ -60,4 +60,7 @@ static inline void tr_lockUnlock( tr_lock_t * l )
#endif
}
int
tr_getDefaultRoute( struct in_addr * addr );
#endif

View File

@ -42,22 +42,12 @@ struct tr_tracker_s
uint64_t dateTry;
uint64_t dateOk;
#define TC_STATUS_IDLE 1
#define TC_STATUS_RESOLVE 2
#define TC_STATUS_CONNECT 4
#define TC_STATUS_RECV 8
char status;
#define TC_ATTEMPT_NOREACH 1
#define TC_ATTEMPT_ERROR 2
#define TC_ATTEMPT_OK 4
char lastAttempt;
tr_resolve_t * resolve;
int socket;
uint8_t * buf;
int size;
int pos;
tr_http_t * http;
int bindPort;
int newPort;
@ -66,8 +56,8 @@ struct tr_tracker_s
uint64_t upload;
};
static void sendQuery ( tr_tracker_t * tc );
static void recvAnswer ( tr_tracker_t * tc );
static tr_http_t * getQuery ( tr_tracker_t * tc );
static void readAnswer ( tr_tracker_t * tc, const char *, int );
tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
{
@ -83,10 +73,7 @@ tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
tc->seeders = -1;
tc->leechers = -1;
tc->status = TC_STATUS_IDLE;
tc->lastAttempt = TC_ATTEMPT_NOREACH;
tc->size = 1024;
tc->buf = malloc( tc->size );
tc->bindPort = *(tor->bindPort);
tc->newPort = -1;
@ -159,11 +146,17 @@ int tr_trackerPulse( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
tr_info_t * inf = &tor->info;
uint64_t now = tr_date();
const char * data;
int len;
if( ( tc->status & TC_STATUS_IDLE ) && shouldConnect( tc ) )
if( ( NULL == tc->http ) && shouldConnect( tc ) )
{
tc->resolve = tr_netResolveInit( inf->trackerAddress );
if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
{
return 0;
}
tc->dateTry = tr_date();
tc->http = getQuery( tc );
tr_inf( "Tracker: connecting to %s:%d (%s)",
inf->trackerAddress, inf->trackerPort,
@ -172,71 +165,29 @@ int tr_trackerPulse( tr_tracker_t * tc )
( tc->stopped ? "sending 'stopped'" :
( 0 < tc->newPort ? "sending 'stopped' to change port" :
"getting peers" ) ) ) );
tc->status = TC_STATUS_RESOLVE;
tc->dateTry = tr_date();
}
if( tc->status & TC_STATUS_RESOLVE )
if( NULL != tc->http )
{
int ret;
struct in_addr addr;
ret = tr_netResolvePulse( tc->resolve, &addr );
if( ret == TR_RESOLVE_WAIT )
switch( tr_httpPulse( tc->http, &data, &len ) )
{
return 0;
case TR_WAIT:
return 0;
case TR_ERROR:
tr_httpClose( tc->http );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->http = NULL;
tc->dateTry = tr_date();
return 0;
case TR_OK:
readAnswer( tc, data, len );
tr_httpClose( tc->http );
tc->http = NULL;
tr_fdSocketClosed( tor->fdlimit, 1 );
break;
}
else
{
tr_netResolveClose( tc->resolve );
}
if( ret == TR_RESOLVE_ERROR )
{
tc->status = TC_STATUS_IDLE;
return 0;
}
if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
{
tc->status = TC_STATUS_IDLE;
return 0;
}
tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) );
if( tc->socket < 0 )
{
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
return 0;
}
tc->status = TC_STATUS_CONNECT;
}
if( tc->status & TC_STATUS_CONNECT )
{
/* We are connecting to the tracker. Try to send the query */
sendQuery( tc );
}
if( tc->status & TC_STATUS_RECV )
{
/* Try to get something */
recvAnswer( tc );
}
if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 )
{
/* Give up if the request wasn't successful within 60 seconds */
tr_inf( "Tracker: timeout reached (60 s)" );
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
}
return 0;
@ -253,13 +204,13 @@ void tr_trackerStopped( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
if( tc->status > TC_STATUS_CONNECT )
if( NULL != tc->http )
{
/* If we are already sendy a query at the moment, we need to
reconnect */
tr_netClose( tc->socket );
tr_httpClose( tc->http );
tc->http = NULL;
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
}
tc->started = 0;
@ -267,39 +218,30 @@ void tr_trackerStopped( tr_tracker_t * tc )
tc->stopped = 1;
/* Even if we have connected recently, reconnect right now */
if( tc->status & TC_STATUS_IDLE )
{
tc->dateTry = 0;
}
tc->dateTry = 0;
}
void tr_trackerClose( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
if( tc->status == TC_STATUS_RESOLVE )
if( NULL != tc->http )
{
tr_netResolveClose( tc->resolve );
}
else if( tc->status > TC_STATUS_RESOLVE )
{
tr_netClose( tc->socket );
tr_httpClose( tc->http );
tr_fdSocketClosed( tor->fdlimit, 1 );
}
free( tc->buf );
free( tc );
}
static void sendQuery( tr_tracker_t * tc )
static tr_http_t * getQuery( tr_tracker_t * tc )
{
tr_torrent_t * tor = tc->tor;
tr_info_t * inf = &tor->info;
char * event;
uint64_t left;
int ret;
uint64_t down;
uint64_t up;
char * event;
uint64_t left;
uint64_t down;
uint64_t up;
assert( tor->downloaded >= tc->download && tor->uploaded >= tc->upload );
down = tor->downloaded - tc->download;
@ -330,119 +272,61 @@ static void sendQuery( tr_tracker_t * tc )
left = tr_cpLeftBytes( tor->completion );
ret = snprintf( (char *) tc->buf, tc->size,
"GET %s?"
"info_hash=%s&"
"peer_id=%s&"
"port=%d&"
"uploaded=%"PRIu64"&"
"downloaded=%"PRIu64"&"
"left=%"PRIu64"&"
"compact=1&"
"numwant=50&"
"key=%s"
"%s "
"HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: Transmission/%d.%d\r\n"
"Connection: close\r\n\r\n",
inf->trackerAnnounce, tor->hashString, tc->id,
tc->bindPort, up, down,
left, tor->key, event, inf->trackerAddress,
VERSION_MAJOR, VERSION_MINOR );
ret = tr_netSend( tc->socket, tc->buf, ret );
if( ret & TR_NET_CLOSE )
{
tr_inf( "Tracker: connection failed" );
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
}
else if( !( ret & TR_NET_BLOCK ) )
{
// printf( "Tracker: sent %s", tc->buf );
tc->status = TC_STATUS_RECV;
tc->pos = 0;
}
return tr_httpClient( TR_HTTP_GET, inf->trackerAddress,
inf->trackerPort,
"%s?"
"info_hash=%s&"
"peer_id=%s&"
"port=%d&"
"uploaded=%"PRIu64"&"
"downloaded=%"PRIu64"&"
"left=%"PRIu64"&"
"compact=1&"
"numwant=50&"
"key=%s"
"%s ",
inf->trackerAnnounce, tor->hashString, tc->id,
tc->bindPort, up, down, left, tor->key, event );
}
static void recvAnswer( tr_tracker_t * tc )
static void readAnswer( tr_tracker_t * tc, const char * data, int len )
{
tr_torrent_t * tor = tc->tor;
int ret;
int i;
int code;
benc_val_t beAll;
benc_val_t * bePeers, * beFoo;
uint8_t * body;
const uint8_t * body;
int bodylen;
int shouldfree;
if( tc->pos == tc->size )
{
tc->size *= 2;
tc->buf = realloc( tc->buf, tc->size );
}
ret = tr_netRecv( tc->socket, &tc->buf[tc->pos],
tc->size - tc->pos );
if( ret & TR_NET_BLOCK )
{
return;
}
if( !( ret & TR_NET_CLOSE ) )
{
// printf( "got %d bytes\n", ret );
tc->pos += ret;
return;
}
tr_netClose( tc->socket );
tr_fdSocketClosed( tor->fdlimit, 1 );
// printf( "connection closed, got total %d bytes\n", tc->pos );
tc->status = TC_STATUS_IDLE;
tc->dateTry = tr_date();
if( tc->pos < 12 || ( 0 != memcmp( tc->buf, "HTTP/1.0 ", 9 ) &&
0 != memcmp( tc->buf, "HTTP/1.1 ", 9 ) ) )
code = tr_httpResponseCode( data, len );
if( 0 > code )
{
/* We don't have a complete HTTP status line */
tr_inf( "Tracker: incomplete HTTP status line" );
/* We don't have a valid HTTP status line */
tr_inf( "Tracker: invalid HTTP status line" );
tc->lastAttempt = TC_ATTEMPT_NOREACH;
return;
}
if( '2' != tc->buf[9] )
if( !TR_HTTP_STATUS_OK( code ) )
{
/* we didn't get a 2xx status code */
tr_err( "Tracker: invalid HTTP status code: %c%c%c",
tc->buf[9], tc->buf[10], tc->buf[11] );
tr_err( "Tracker: invalid HTTP status code: %i", code );
tc->lastAttempt = TC_ATTEMPT_ERROR;
return;
}
/* find the end of the http headers */
body = tr_memmem( tc->buf, tc->pos, "\015\012\015\012", 4 );
if( NULL != body )
{
body += 4;
}
/* hooray for trackers that violate the HTTP spec */
else if( NULL != ( body = tr_memmem( tc->buf, tc->pos, "\015\015", 2 ) ) ||
NULL != ( body = tr_memmem( tc->buf, tc->pos, "\012\012", 2 ) ) )
{
body += 2;
}
else
body = (uint8_t *) tr_httpParse( data, len, NULL );
if( NULL == body )
{
tr_err( "Tracker: could not find end of HTTP headers" );
tc->lastAttempt = TC_ATTEMPT_NOREACH;
return;
}
bodylen = tc->pos - (body - tc->buf);
bodylen = len - (body - (const uint8_t*)data);
/* Find and load the dictionary */
shouldfree = 0;
@ -610,13 +494,11 @@ int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
{
tr_info_t * inf = &tor->info;
int s, i, ret;
uint8_t buf[1024];
benc_val_t scrape, * val1, * val2;
struct in_addr addr;
uint64_t date;
int pos, len;
tr_resolve_t * resolve;
tr_http_t * http;
const char * data, * body;
int datalen, bodylen;
int code, ii;
benc_val_t scrape, * val1, * val2;
if( !tor->scrape[0] )
{
@ -624,133 +506,97 @@ int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers )
return 1;
}
resolve = tr_netResolveInit( inf->trackerAddress );
for( date = tr_date();; )
{
ret = tr_netResolvePulse( resolve, &addr );
if( ret == TR_RESOLVE_OK )
{
tr_netResolveClose( resolve );
break;
}
if( ret == TR_RESOLVE_ERROR ||
( ret == TR_RESOLVE_WAIT && tr_date() > date + 10000 ) )
{
tr_err( "Could not resolve %s", inf->trackerAddress );
tr_netResolveClose( resolve );
return 1;
}
tr_wait( 10 );
}
http = tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
"%s?info_hash=%s", tor->scrape, tor->hashString );
s = tr_netOpen( addr, htons( inf->trackerPort ) );
if( s < 0 )
data = NULL;
while( NULL == data )
{
return 1;
}
len = snprintf( (char *) buf, sizeof( buf ),
"GET %s?info_hash=%s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n\r\n",
tor->scrape, tor->hashString,
inf->trackerAddress );
for( date = tr_date();; )
{
ret = tr_netSend( s, buf, len );
if( ret & TR_NET_CLOSE )
switch( tr_httpPulse( http, &data, &datalen ) )
{
tr_err( "Could not connect to tracker" );
tr_netClose( s );
return 1;
}
else if( ret & TR_NET_BLOCK )
{
if( tr_date() > date + 10000 )
{
tr_err( "Could not connect to tracker" );
tr_netClose( s );
case TR_WAIT:
break;
case TR_ERROR:
tr_httpClose( http );
return 1;
}
}
else
{
break;
case TR_OK:
if( NULL == data || 0 >= datalen )
{
tr_httpClose( http );
return 1;
}
break;
}
tr_wait( 10 );
}
pos = 0;
for( date = tr_date();; )
code = tr_httpResponseCode( data, datalen );
if( !TR_HTTP_STATUS_OK( code ) )
{
ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos );
if( ret & TR_NET_CLOSE )
{
break;
}
else if( ret & TR_NET_BLOCK )
{
if( tr_date() > date + 10000 )
{
tr_err( "Could not read from tracker" );
tr_netClose( s );
return 1;
}
}
else
{
pos += ret;
}
tr_wait( 10 );
}
if( pos < 1 )
{
tr_err( "Could not read from tracker" );
tr_netClose( s );
tr_httpClose( http );
return 1;
}
for( i = 0; i < pos - 8; i++ )
body = tr_httpParse( data, datalen , NULL );
if( NULL == body )
{
if( !memcmp( &buf[i], "d5:files", 8 ) )
tr_httpClose( http );
return 1;
}
bodylen = datalen - ( body - data );
for( ii = 0; ii < bodylen - 8; ii++ )
{
if( !memcmp( body + ii, "d5:files", 8 ) )
{
break;
}
}
if( i >= pos - 8 )
if( ii >= bodylen - 8 )
{
tr_httpClose( http );
return 1;
}
if( tr_bencLoad( &buf[i], pos - i, &scrape, NULL ) )
if( tr_bencLoad( body + ii, bodylen - ii, &scrape, NULL ) )
{
tr_httpClose( http );
return 1;
}
val1 = tr_bencDictFind( &scrape, "files" );
if( !val1 )
{
tr_bencFree( &scrape );
tr_httpClose( http );
return 1;
}
val1 = &val1->val.l.vals[1];
if( !val1 )
{
tr_bencFree( &scrape );
tr_httpClose( http );
return 1;
}
val2 = tr_bencDictFind( val1, "complete" );
if( !val2 )
{
tr_bencFree( &scrape );
tr_httpClose( http );
return 1;
}
*seeders = val2->val.i;
val2 = tr_bencDictFind( val1, "incomplete" );
if( !val2 )
{
tr_bencFree( &scrape );
tr_httpClose( http );
return 1;
}
*leechers = val2->val.i;
tr_bencFree( &scrape );
tr_httpClose( http );
return 0;
}

View File

@ -74,6 +74,8 @@ tr_handle_t * tr_init()
h->download = tr_rcInit();
h->fdlimit = tr_fdInit();
h->choking = tr_chokingInit( h );
h->natpmp = tr_natpmpInit( h->fdlimit );
h->upnp = tr_upnpInit( h->fdlimit );
h->bindPort = -1;
h->bindSocket = -1;
@ -104,7 +106,16 @@ void tr_setBindPort( tr_handle_t * h, int port )
if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
{
/* XXX should handle failure here in a better way */
sock = tr_netBind( port );
sock = tr_netBindTCP( port );
if( 0 > sock)
{
tr_fdSocketClosed( h->fdlimit, 0 );
}
else
{
tr_inf( "Bound listening port %d", port );
listen( sock, 5 );
}
}
#else
return;
@ -132,9 +143,53 @@ void tr_setBindPort( tr_handle_t * h, int port )
h->bindSocket = sock;
tr_natpmpForwardPort( h->natpmp, port );
tr_upnpForwardPort( h->upnp, port );
tr_lockUnlock( &h->acceptLock );
}
void tr_natTraversalEnable( tr_handle_t * h )
{
tr_natpmpStart( h->natpmp );
tr_upnpStart( h->upnp );
}
void tr_natTraversalDisable( tr_handle_t * h )
{
tr_natpmpStop( h->natpmp );
tr_upnpStop( h->upnp );
}
int tr_natTraversalStatus( tr_handle_t * h )
{
int statuses[] = {
TR_NAT_TRAVERSAL_MAPPED,
TR_NAT_TRAVERSAL_MAPPING,
TR_NAT_TRAVERSAL_UNMAPPING,
TR_NAT_TRAVERSAL_ERROR,
TR_NAT_TRAVERSAL_NOTFOUND,
TR_NAT_TRAVERSAL_DISABLED,
-1,
};
int natpmp, upnp, ii;
natpmp = tr_natpmpStatus( h->natpmp );
upnp = tr_upnpStatus( h->upnp );
for( ii = 0; 0 <= statuses[ii]; ii++ )
{
if( statuses[ii] == natpmp || statuses[ii] == upnp )
{
return statuses[ii];
}
}
assert( 0 );
return TR_NAT_TRAVERSAL_ERROR;
}
/***********************************************************************
* tr_setUploadLimit
***********************************************************************
@ -629,6 +684,8 @@ void tr_torrentClose( tr_handle_t * h, tr_torrent_t * tor )
void tr_close( tr_handle_t * h )
{
acceptStop( h );
tr_natpmpClose( h->natpmp );
tr_upnpClose( h->upnp );
tr_chokingClose( h->choking );
tr_fdClose( h->fdlimit );
tr_rcClose( h->upload );
@ -735,6 +792,10 @@ static void acceptLoop( void * _h )
{
date1 = tr_date();
/* do NAT-PMP and UPnP pulses here since there's nowhere better */
tr_natpmpPulse( h->natpmp );
tr_upnpPulse( h->upnp );
/* Check for incoming connections */
if( h->bindSocket > -1 &&
h->acceptPeerCount < TR_MAX_PEER_COUNT &&

View File

@ -99,10 +99,35 @@ char * tr_getPrefsDirectory();
/***********************************************************************
* tr_setBindPort
***********************************************************************
* Sets the port to listen for incoming peer connections
* Sets the port to listen for incoming peer connections.
* This can be safely called even with active torrents.
**********************************************************************/
void tr_setBindPort( tr_handle_t *, int );
/***********************************************************************
* tr_natTraversalEnable
* tr_natTraversalDisable
***********************************************************************
* Enable or disable NAT traversal using NAT-PMP or UPnP IGD.
**********************************************************************/
void tr_natTraversalEnable( tr_handle_t * );
void tr_natTraversalDisable( tr_handle_t * );
/***********************************************************************
* tr_natTraversalStatus
***********************************************************************
* Return the status of NAT traversal
**********************************************************************/
#define TR_NAT_TRAVERSAL_MAPPING 1
#define TR_NAT_TRAVERSAL_MAPPED 2
#define TR_NAT_TRAVERSAL_NOTFOUND 3
#define TR_NAT_TRAVERSAL_ERROR 4
#define TR_NAT_TRAVERSAL_UNMAPPING 5
#define TR_NAT_TRAVERSAL_DISABLED 6
#define TR_NAT_TRAVERSAL_IS_DISABLED( st ) \
( TR_NAT_TRAVERSAL_DISABLED == (st) || TR_NAT_TRAVERSAL_UNMAPPING == (st) )
int tr_natTraversalStatus( tr_handle_t * );
/***********************************************************************
* tr_setUploadLimit
***********************************************************************

1328
libtransmission/upnp.c Normal file

File diff suppressed because it is too large Load Diff

38
libtransmission/upnp.h Normal file
View File

@ -0,0 +1,38 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef TR_UPNP_H
#define TR_UPNP_H 1
typedef struct tr_upnp_s tr_upnp_t;
tr_upnp_t * tr_upnpInit( tr_fd_t * );
void tr_upnpStart( tr_upnp_t * );
void tr_upnpStop( tr_upnp_t * );
int tr_upnpStatus( tr_upnp_t * );
void tr_upnpForwardPort( tr_upnp_t *, int );
void tr_upnpPulse( tr_upnp_t * );
void tr_upnpClose( tr_upnp_t * );
#endif

View File

@ -24,6 +24,8 @@
#include "transmission.h"
#define SPRINTF_BUFSIZE 100
static tr_lock_t * messageLock = NULL;
static int messageLevel = 0;
static int messageQueuing = 0;
@ -96,8 +98,9 @@ void tr_freeMessageList( tr_msg_list_t * list )
void tr_msg( int level, char * msg, ... )
{
va_list args;
va_list args1, args2;
tr_msg_list_t * newmsg;
int len1, len2;
assert( NULL != messageLock );
tr_lockLock( messageLock );
@ -112,7 +115,7 @@ void tr_msg( int level, char * msg, ... )
if( messageLevel >= level )
{
va_start( args, msg );
va_start( args1, msg );
if( messageQueuing )
{
newmsg = calloc( 1, sizeof( *newmsg ) );
@ -120,7 +123,11 @@ void tr_msg( int level, char * msg, ... )
{
newmsg->level = level;
newmsg->when = time( NULL );
vasprintf( &newmsg->message, msg, args );
len1 = len2 = 0;
va_start( args2, msg );
tr_vsprintf( &newmsg->message, &len1, &len2, msg,
args1, args2 );
va_end( args2 );
if( NULL == newmsg->message )
{
free( newmsg );
@ -134,10 +141,10 @@ void tr_msg( int level, char * msg, ... )
}
else
{
vfprintf( stderr, msg, args );
vfprintf( stderr, msg, args1 );
fputc( '\n', stderr );
}
va_end( args );
va_end( args1 );
}
tr_lockUnlock( messageLock );
@ -234,3 +241,96 @@ int tr_mkdir( char * path )
return 0;
}
#define UPPER( cc ) \
( 'a' <= (cc) && 'z' >= (cc) ? (cc) - ( 'a' - 'A' ) : (cc) )
int tr_strncasecmp( const char * first, const char * second, int len )
{
int ii;
char firstchar, secondchar;
if( 0 > len )
{
len = strlen( first );
ii = strlen( second );
len = MIN( len, ii );
}
for( ii = 0; ii < len; ii++ )
{
if( first[ii] != second[ii] )
{
firstchar = UPPER( first[ii] );
secondchar = UPPER( second[ii] );
if( firstchar > secondchar )
{
return 1;
}
else if( firstchar < secondchar )
{
return -1;
}
}
if( '\0' == first[ii] )
{
/* if first[ii] is '\0' then second[ii] is too */
return 0;
}
}
return 0;
}
int tr_sprintf( char ** buf, int * used, int * max, const char * format, ... )
{
va_list ap1, ap2;
int ret;
va_start( ap1, format );
va_start( ap2, format );
ret = tr_vsprintf( buf, used, max, format, ap1, ap2 );
va_end( ap2 );
va_end( ap1 );
return ret;
}
int tr_vsprintf( char ** buf, int * used, int * max, const char * fmt,
va_list ap1, va_list ap2 )
{
int want;
char * newbuf;
want = vsnprintf( NULL, 0, fmt, ap1 );
while( *used + want + 1 > *max )
{
*max += SPRINTF_BUFSIZE;
newbuf = realloc( *buf, *max );
if( NULL == newbuf )
{
return 1;
}
*buf = newbuf;
}
*used += vsnprintf( *buf + *used, *max - *used, fmt, ap2 );
return 0;
}
char *
tr_dupstr( const char * base, int len )
{
char * ret;
ret = malloc( len + 1 );
if( NULL != ret )
{
memcpy( ret, base, len );
ret[len] = '\0';
}
return ret;
}

View File

@ -30,7 +30,7 @@ void tr_msgInit( void );
#define tr_err( a... ) tr_msg( TR_MSG_ERR, ## a )
#define tr_inf( a... ) tr_msg( TR_MSG_INF, ## a )
#define tr_dbg( a... ) tr_msg( TR_MSG_DBG, ## a )
void tr_msg ( int level, char * msg, ... );
void tr_msg ( int level, char * msg, ... ) PRINTF( 2, 3 );
int tr_rand ( int );
@ -44,6 +44,32 @@ void * tr_memmem( const void *, size_t, const void *, size_t );
**********************************************************************/
int tr_mkdir( char * path );
/***********************************************************************
* tr_strcasecmp
***********************************************************************
* A case-insensitive strncmp()
**********************************************************************/
#define tr_strcasecmp( ff, ss ) ( tr_strncasecmp( (ff), (ss), -1 ) )
int tr_strncasecmp( const char * first, const char * second, int len );
/***********************************************************************
* tr_sprintf
***********************************************************************
* Appends to the end of a buffer using printf formatting,
* growing the buffer if needed
**********************************************************************/
int tr_sprintf( char ** buf, int * used, int * max,
const char * format, ... ) PRINTF( 4, 5 );
/* gee, it sure would be nice if BeOS had va_copy() */
int tr_vsprintf( char **, int *, int *, const char *, va_list, va_list );
/***********************************************************************
* tr_dupstr
***********************************************************************
* Creates a nul-terminated string
**********************************************************************/
char * tr_dupstr( const char * base, int len );
/***********************************************************************
* tr_date
***********************************************************************

424
libtransmission/xml.c Normal file
View File

@ -0,0 +1,424 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "transmission.h"
/* http://www.w3.org/TR/2004/REC-xml-20040204/ */
#define WS( cc ) \
( ' ' == (cc) || '\t' == (cc) || '\n' == (cc) || '\r' == (cc) )
#define TAGBEGIN '<'
#define TAGEND '>'
#define TAGCLOSE '/'
#define NAMESPACESEP ':'
#define SQUOTEC '\''
#define DQUOTEC '"'
#define SQUOTES "'"
#define DQUOTES "\""
#define COMMENTBEGIN "<!--"
#define COMMENTEND "-->"
#define PROCINSTBEGIN "<?"
#define PROCINSTEND "?>"
#define CDATABEGIN "<![CDATA["
#define CDATAEND "]]>"
#define BANGBEGIN "<!"
#define BANGEND ">"
#define CHECKNULL( bb, ee, rr ) \
{ if( NULL == (bb) || (ee) <= (bb) ) return (rr); }
#define justskip( bb, ee, ap, ot, ct ) \
( skipthingy( (bb), (ee), (ap), (ot), (ct), NULL, NULL ) )
static char *
catrange( char * str, const char * begin, const char * end );
static int
skipall( const char * begin, const char * end, const char ** afterpos );
static int
nexttag( const char * begin, const char * end, const char ** tagpos );
static int
overtag( const char * begin, const char * end, const char ** overpos );
static int
tagname( const char * begin, const char * end,
const char ** tagstart, const char ** namestart, int * namelen );
static int
skipthingy( const char * begin, const char * end, const char ** afterpos,
const char * openthingy, const char * closethingy,
const char ** databegin, const char ** dataend );
/* XXX check document charset, in http headers and/or <?xml> tag */
const char *
tr_xmlFindTag( const char * begin, const char * end, const char * tag )
{
const char * name;
int len;
CHECKNULL( begin, end, NULL );
while( tagname( begin, end, &begin, &name, &len ) )
{
assert( NULL != begin && NULL != name && 0 < len );
if( 0 == tr_strncasecmp( tag, name, len ) )
{
return begin;
}
begin = tr_xmlSkipTag( begin, end );
}
return NULL;
}
const char *
tr_xmlTagName( const char * begin, const char * end, int * len )
{
CHECKNULL( begin, end, NULL );
if( tagname( begin, end, NULL, &begin, len ) )
{
return begin;
}
return NULL;
}
const char *
tr_xmlTagContents( const char * begin, const char * end )
{
CHECKNULL( begin, end, NULL );
if( nexttag( begin, end, &begin ) && overtag( begin, end, &begin ) )
{
begin = NULL;
}
return begin;
}
int
tr_xmlVerifyContents( const char * begin, const char * end, const char * data,
int ignorecase )
{
int len;
CHECKNULL( begin, end, 1 );
len = strlen( data );
while( end > begin && WS( *begin ) )
{
begin++;
}
if( end - begin > len )
{
if( ignorecase )
{
return ( 0 != tr_strncasecmp( begin, data, len ) );
}
else
{
return ( 0 != memcmp( begin, data, len ) );
}
}
return 1;
}
const char *
tr_xmlSkipTag( const char * begin, const char * end )
{
CHECKNULL( begin, end, NULL );
if( nexttag( begin, end, &begin ) )
{
if( overtag( begin, end, &begin ) )
{
return begin;
}
while( NULL != begin )
{
if( nexttag( begin, end, &begin ) )
{
begin = tr_xmlSkipTag( begin, end );
}
else
{
overtag( begin, end, &begin );
return begin;
}
}
}
return NULL;
}
char *
tr_xmlDupContents( const char * begin, const char * end )
{
const char * ii, * cbegin, * cend;
char * ret;
int len;
CHECKNULL( begin, end, NULL );
ret = NULL;
len = strlen( CDATABEGIN );
while( end > begin )
{
ii = memchr( begin, TAGBEGIN, end - begin );
if( NULL == ii )
{
free( ret );
return NULL;
}
/* XXX expand entity references and such here */
ret = catrange( ret, begin, ii );
if( NULL == ret )
{
return NULL;
}
if( !skipthingy( ii, end, &begin, CDATABEGIN, CDATAEND,
&cbegin, &cend ) )
{
ret = catrange( ret, cbegin, cend );
if( NULL == ret )
{
return NULL;
}
}
else if( skipall( ii, end, &begin ) )
{
if( end > ii + 1 && TAGCLOSE == ii[1] )
{
return ret;
}
begin = tr_xmlSkipTag( ii, end );
}
}
free( ret );
return NULL;
}
static char *
catrange( char * str, const char * begin, const char * end )
{
int len;
char * ret;
if( NULL == str )
{
return tr_dupstr( begin, end - begin );
}
len = strlen( str );
ret = realloc( str, len + end - begin + 1 );
if( NULL == ret )
{
free( str );
return NULL;
}
memcpy( ret + len, begin, end - begin );
ret[len + end - begin] = '\0';
return ret;
}
static int
skipall( const char * begin, const char * end, const char ** afterpos )
{
return ( justskip( begin, end, afterpos, COMMENTBEGIN, COMMENTEND ) &&
justskip( begin, end, afterpos, CDATABEGIN, CDATAEND ) &&
justskip( begin, end, afterpos, PROCINSTBEGIN, PROCINSTEND ) &&
justskip( begin, end, afterpos, BANGBEGIN, BANGEND ) );
}
/* returns true if a tag was found and it's a start or empty element tag */
static int
nexttag( const char * begin, const char * end, const char ** tagpos )
{
CHECKNULL( begin, end, 0 );
while( end > begin )
{
begin = memchr( begin, TAGBEGIN, end - begin );
CHECKNULL( begin, end, 0 );
if( justskip( begin, end, &begin, COMMENTBEGIN, COMMENTEND ) &&
justskip( begin, end, &begin, CDATABEGIN, CDATAEND ) &&
justskip( begin, end, &begin, PROCINSTBEGIN, PROCINSTEND ) &&
justskip( begin, end, &begin, BANGBEGIN, BANGEND ) )
{
*tagpos = begin;
begin++;
if( end > begin )
{
return ( TAGCLOSE != *begin );
}
break;
}
}
*tagpos = NULL;
return 0;
}
/* returns true if the tag is an empty element such as <foo/> */
static int
overtag( const char * begin, const char * end, const char ** overpos )
{
const char * ii;
assert( NULL != begin && end > begin && TAGBEGIN == *begin );
ii = begin + 1;
while( end > ii )
{
switch( *ii )
{
case DQUOTEC:
justskip( ii, end, &ii, DQUOTES, DQUOTES );
break;
case SQUOTEC:
justskip( ii, end, &ii, SQUOTES, SQUOTES );
break;
case TAGEND:
*overpos = ii + 1;
for( ii--; begin < ii; ii-- )
{
if( TAGCLOSE == *ii )
{
return 1;
}
if( !WS( *ii ) )
{
break;
}
}
return 0;
default:
ii++;
break;
}
}
*overpos = NULL;
return 0;
}
static int
tagname( const char * begin, const char * end,
const char ** tagstart, const char ** namestart, int * namelen )
{
const char * name, * ii;
CHECKNULL( begin, end, 0 );
if( nexttag( begin, end, &begin ) )
{
assert( NULL != begin && TAGBEGIN == *begin );
ii = begin + 1;
while( end > ii && WS( *ii ) )
{
ii++;
}
name = ii;
while( end > ii && TAGEND != *ii && !WS( *ii ) )
{
if( NAMESPACESEP == *ii )
{
name = ii + 1;
}
ii++;
}
if( end > ii && ii > name )
{
if( NULL != tagstart )
{
*tagstart = begin;
}
if( NULL != namestart )
{
*namestart = name;
}
if( NULL != namelen )
{
*namelen = ii - name;
}
return 1;
}
}
return 0;
}
static int
skipthingy( const char * begin, const char * end, const char ** afterpos,
const char * openthingy, const char * closethingy,
const char ** databegin, const char ** dataend )
{
int len;
CHECKNULL( begin, end, 1 );
len = strlen( openthingy );
if( 0 != memcmp( begin, openthingy, MIN( end - begin, len ) ) )
{
return 1;
}
if( NULL != afterpos )
{
*afterpos = NULL;
}
if( NULL != databegin )
{
*databegin = NULL;
}
if( NULL != dataend )
{
*dataend = NULL;
}
if( end - begin <= len )
{
return 0;
}
begin += len;
if( NULL != databegin )
{
*databegin = begin;
}
len = strlen( closethingy );
begin = tr_memmem( begin, end - begin, closethingy, len );
if( NULL != dataend )
{
*dataend = begin;
}
if( NULL != afterpos && NULL != begin )
{
*afterpos = ( begin + len >= end ? NULL : begin + len );
}
return 0;
}

57
libtransmission/xml.h Normal file
View File

@ -0,0 +1,57 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef TR_XML_H
#define TR_XML_H 1
const char *
tr_xmlFindTag( const char * begin, const char * end, const char * tag );
const char *
tr_xmlTagName( const char * begin, const char * end, int * len );
const char *
tr_xmlTagContents( const char * begin, const char * end );
#define tr_xmlFindTagContents( bb, ee, tt ) \
( tr_xmlTagContents( tr_xmlFindTag( (bb), (ee), (tt) ), (ee) ) )
int
tr_xmlVerifyContents( const char * begin, const char * end, const char * data,
int ignorecase );
#define tr_xmlFindTagVerifyContents( bb, ee, tt, dd, ic ) \
( tr_xmlVerifyContents( tr_xmlFindTagContents( (bb), (ee), (tt) ), \
(ee), (dd), (ic) ) )
const char *
tr_xmlSkipTag( const char * begin, const char * end );
char *
tr_xmlDupContents( const char * begin, const char * end );
#define tr_xmlDupTagContents( bb, ee, tt ) \
( tr_xmlDupContents( tr_xmlFindTagContents( (bb), (ee), (tt) ), (ee) ) )
#endif

View File

@ -30,6 +30,7 @@
#import "PrefsController.h"
#import "InfoWindowController.h"
#import "MessageWindowController.h"
#import "PiecesWindowController.h"
#import "Badger.h"
#import "ImageBackgroundView.h"
#import "BarButton.h"
@ -49,6 +50,7 @@
NSUserDefaults * fDefaults;
InfoWindowController * fInfoController;
MessageWindowController * fMessageController;
PiecesWindowController * fPiecesWindowController;
IBOutlet NSWindow * fWindow;
IBOutlet NSScrollView * fScrollView;
@ -105,9 +107,12 @@
- (void) resumeSelectedTorrents: (id) sender;
- (void) resumeAllTorrents: (id) sender;
- (void) resumeWaitingTorrents: (id) sender;
- (void) resumeTorrents: (NSArray *) torrents;
- (void) resumeSelectedTorrentsNoWait: (id) sender;
- (void) resumeWaitingTorrents: (id) sender;
- (void) resumeTorrentsNoWait: (NSArray *) torrents;
- (void) stopSelectedTorrents: (id) sender;
- (void) stopAllTorrents: (id) sender;
- (void) stopTorrents: (NSArray *) torrents;
@ -136,6 +141,7 @@
- (void) setInfoTab: (id) sender;
- (void) showMessageWindow: (id) sender;
- (void) showPiecesView: (id) sender;
- (void) updateControlTint: (NSNotification *) notification;
@ -172,8 +178,6 @@
- (void) attemptToStartAuto: (Torrent *) torrent;
- (void) attemptToStartMultipleAuto: (NSArray *) torrents;
- (void) reloadInspectorSettings: (NSNotification *) notification;
- (void) checkAutoImportDirectory;
- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument;

View File

@ -86,6 +86,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
fMessageController = [[MessageWindowController alloc] initWithWindowNibName: @"MessageWindow"];
fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
fPiecesWindowController = [[PiecesWindowController alloc] initWithWindowNibName: @"PiecesWindow"];
fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow" handle: fLib];
fBadger = [[Badger alloc] init];
@ -100,13 +101,15 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[fTorrents release];
[fDisplayedTorrents release];
[fInfoController release];
[fMessageController release];
[fPiecesWindowController release];
[fPrefsController release];
[fToolbar release];
[fInfoController release];
[fPrefsController release];
[fTorrents release];
[fDisplayedTorrents release];
[fBadger release];
[fSortType release];
@ -332,19 +335,11 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
//check all torrents for starting
[nc addObserver: self selector: @selector(globalStartSettingChange:)
name: @"GlobalStartSettingChange" object: nil];
//check if torrent should now start
[nc addObserver: self selector: @selector(torrentStartSettingChange:)
name: @"TorrentStartSettingChange" object: nil];
//check if torrent should now start
[nc addObserver: self selector: @selector(torrentStoppedForRatio:)
name: @"TorrentStoppedForRatio" object: nil];
//change that just impacts the inspector
[nc addObserver: self selector: @selector(reloadInspectorSettings:)
name: @"TorrentSettingChange" object: nil];
//change that just impacts the dock badge
[nc addObserver: self selector: @selector(resetDockBadge:)
name: @"DockBadgeChange" object: nil];
@ -364,6 +359,9 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
if ([fDefaults boolForKey: @"InfoVisible"])
[self showInfo: nil];
if ([fDefaults boolForKey: @"PiecesViewerVisible"])
[self showPiecesView: nil];
//timer to auto toggle speed limit
[self autoSpeedLimitChange: nil];
fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
@ -425,8 +423,12 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateTorrentHistory];
[fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
//disable NAT traversal
tr_natTraversalDisable(fLib);
//remember window states and close all windows
[fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
[fDefaults setBool: [[fPiecesWindowController window] isVisible] forKey: @"PiecesViewerVisible"];
[[NSApp windows] makeObjectsPerformSelector: @selector(close)];
[self showStatusBar: NO animate: NO];
[self showFilterBar: NO animate: NO];
@ -438,13 +440,13 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
if (fUpdateInProgress)
return;
//wait for running transfers to stop (5 second timeout)
//wait for running transfers to stop (5 second timeout) and for NAT to be disabled
NSDate * start = [NSDate date];
BOOL timeUp = NO;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
while (!timeUp && (torrent = [enumerator nextObject]))
while (!timeUp && ((torrent = [enumerator nextObject]) || tr_natTraversalStatus(fLib) != TR_NAT_TRAVERSAL_DISABLED))
while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
{
usleep(100000);
@ -543,8 +545,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[panel setMessage: [NSString stringWithFormat: @"Select the download folder for \"%@\"", [torrent name]]];
NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
torrent, @"Torrent", files, @"Files", nil];
NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: torrent, @"Torrent", files, @"Files", nil];
[panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
@ -626,6 +627,25 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self resumeTorrents: fTorrents];
}
- (void) resumeTorrents: (NSArray *) torrents
{
NSEnumerator * enumerator = [torrents objectEnumerator];
Torrent * torrent;
while ((torrent = [enumerator nextObject]))
[torrent setWaitToStart: YES];
[self attemptToStartMultipleAuto: torrents];
[self updateUI: nil];
[self applyFilter: nil];
[self updateTorrentHistory];
}
- (void) resumeSelectedTorrentsNoWait: (id) sender
{
[self resumeTorrentsNoWait: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
}
- (void) resumeWaitingTorrents: (id) sender
{
NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
@ -636,16 +656,15 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
if ([torrent waitingToStart])
[torrents addObject: torrent];
[self resumeTorrents: torrents];
[self resumeTorrentsNoWait: torrents];
}
- (void) resumeTorrents: (NSArray *) torrents
- (void) resumeTorrentsNoWait: (NSArray *) torrents
{
[torrents makeObjectsPerformSelector: @selector(startTransfer)];
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
@ -671,7 +690,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
@ -918,6 +936,17 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[fMessageController showWindow: nil];
}
- (void) showPiecesView: (id) sender
{
if ([[fPiecesWindowController window] isVisible])
[fPiecesWindowController close];
else
{
[fPiecesWindowController updateView: NO];
[[fPiecesWindowController window] orderFront: nil];
}
}
- (void) updateControlTint: (NSNotification *) notification
{
if (fSpeedLimitEnabled)
@ -947,6 +976,10 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
//update non-constant parts of info window
if ([[fInfoController window] isVisible])
[fInfoController updateInfoStats];
//update pieces viewer
if ([[fPiecesWindowController window] isVisible])
[fPiecesWindowController updateView: NO];
//badge dock
[fBadger updateBadgeWithCompleted: fCompleted uploadRate: uploadRate downloadRate: downloadRate];
@ -1387,17 +1420,16 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
- (void) checkToStartWaiting: (Torrent *) finishedTorrent
{
//don't try to start a transfer if there should be none waiting
if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
if (![fDefaults boolForKey: @"Queue"])
return;
int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
int desiredActive = [fDefaults integerForKey: @"QueueDownloadNumber"];
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent, * torrentToStart = nil;
@ -1432,7 +1464,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
}
@ -1443,7 +1474,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
@ -1453,14 +1483,14 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateUI: nil];
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[self updateTorrentHistory];
}
- (void) torrentStoppedForRatio: (NSNotification *) notification
{
[self applyFilter: nil];
[fInfoController updateInfoStatsAndSettings];
[fInfoController updateInfoStats];
[fInfoController updateInfoSettings];
if ([fDefaults boolForKey: @"PlaySeedingSound"])
{
@ -1481,8 +1511,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
//will try to start, taking into consideration the start preference
- (void) attemptToStartMultipleAuto: (NSArray *) torrents
{
NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
if ([startSetting isEqualToString: @"Start"])
if (![fDefaults boolForKey: @"Queue"])
{
NSEnumerator * enumerator = [torrents objectEnumerator];
Torrent * torrent;
@ -1492,12 +1521,9 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
return;
}
else if (![startSetting isEqualToString: @"Wait"])
return;
else;
//determine the number of downloads needed to start
int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
int desiredActive = [fDefaults integerForKey: @"QueueDownloadNumber"];
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
@ -1543,11 +1569,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
}
}
- (void) reloadInspectorSettings: (NSNotification *) notification
{
[fInfoController updateInfoStatsAndSettings];
}
-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
{
if ([notification isEqualToString: UKFileWatcherWriteNotification])
@ -1586,12 +1607,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
return [fDisplayedTorrents count];
}
/*- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
forTableColumn: (NSTableColumn *) tableColumn row: (int) row
{
[cell setTorrent: [fDisplayedTorrents objectAtIndex: row]];
}*/
- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row
{
return [[fDisplayedTorrents objectAtIndex: row] infoForCurrentView];
@ -1712,7 +1727,11 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) tableViewSelectionDidChange: (NSNotification *) notification
{
[fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
NSArray * torrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
[fInfoController updateInfoForTorrents: torrents];
Torrent * torrent = [torrents count] == 1 ? [torrents objectAtIndex: 0] : nil;
[fPiecesWindowController setTorrent: torrent];
}
- (void) toggleSmallView: (id) sender
@ -1981,7 +2000,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
if ([torrent isActive])
if ([torrent isActive] || [torrent waitingToStart])
return YES;
return NO;
}
@ -1992,7 +2011,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
if ([torrent isPaused])
if ([torrent isPaused] && ![torrent waitingToStart])
return YES;
return NO;
}
@ -2005,20 +2024,27 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
unsigned int i;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
if ([[fDisplayedTorrents objectAtIndex: i] isActive])
{
torrent = [fDisplayedTorrents objectAtIndex: i];
if ([torrent isActive] || [torrent waitingToStart])
return YES;
}
return NO;
}
//enable resume item
if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
{
Torrent * torrent;
NSIndexSet * indexSet = [fTableView selectedRowIndexes];
unsigned int i;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
if ([[fDisplayedTorrents objectAtIndex: i] isPaused])
{
torrent = [fDisplayedTorrents objectAtIndex: i];
if ([torrent isPaused] && ![torrent waitingToStart])
return YES;
}
return NO;
}
@ -2050,6 +2076,16 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
return YES;
}
//enable show pieces window
if (action == @selector(showPiecesView:))
{
NSString * title = [[fPiecesWindowController window] isVisible] ? @"Hide Pieces Viewer" : @"Show Pieces Viewer";
if (![[menuItem title] isEqualToString: title])
[menuItem setTitle: title];
return YES;
}
//enable prev/next inspector tab
if (action == @selector(setInfoTab:))
return [[fInfoController window] isVisible];
@ -2132,7 +2168,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
if ([torrent isActive])
if ([torrent isActive] || [torrent waitingToStart])
return YES;
return NO;
}
@ -2143,15 +2179,15 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
if ([torrent isPaused])
if ([torrent isPaused] && ![torrent waitingToStart])
return YES;
return NO;
}
//enable resume waiting item
//enable resume all waiting item
if (action == @selector(resumeWaitingTorrents:))
{
if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
if (![fDefaults boolForKey: @"Queue"])
return NO;
Torrent * torrent;
@ -2161,6 +2197,25 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
return YES;
return NO;
}
//enable resume selected waiting item
if (action == @selector(resumeSelectedTorrentsNoWait:))
{
if (![fDefaults boolForKey: @"Queue"])
return NO;
Torrent * torrent;
NSIndexSet * indexSet = [fTableView selectedRowIndexes];
unsigned int i;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
torrent = [fDisplayedTorrents objectAtIndex: i];
if ([torrent isPaused])
return YES;
}
return NO;
}
//enable pause item
if (action == @selector(stopSelectedTorrents:))
@ -2175,7 +2230,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
torrent = [fDisplayedTorrents objectAtIndex: i];
if ([torrent isActive])
if ([torrent isActive] || [torrent waitingToStart])
return YES;
}
return NO;
@ -2194,7 +2249,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
torrent = [fDisplayedTorrents objectAtIndex: i];
if ([torrent isPaused])
if ([torrent isPaused] && ![torrent waitingToStart])
return YES;
}
return NO;

View File

@ -52,10 +52,18 @@
<string>Constant</string>
<key>MoveFolder</key>
<string>~/Desktop</string>
<key>NatTraversal</key>
<true/>
<key>PiecesViewerVisible</key>
<false/>
<key>PlayDownloadSound</key>
<true/>
<key>PlaySeedingSound</key>
<false/>
<key>Queue</key>
<false/>
<key>QueueDownloadNumber</key>
<integer>3</integer>
<key>RatioCheck</key>
<false/>
<key>RatioLimit</key>
@ -86,8 +94,8 @@
<integer>10</integer>
<key>SpeedLimitUploadLimit</key>
<integer>10</integer>
<key>StartSetting</key>
<string>Start</string>
<key>StartAtOpen</key>
<true/>
<key>StatusBar</key>
<true/>
<key>UpdateCheck</key>
@ -96,7 +104,5 @@
<integer>20</integer>
<key>UseAdvancedBar</key>
<false/>
<key>WaitToStartNumber</key>
<integer>3</integer>
</dict>
</plist>

View File

@ -8,12 +8,7 @@
},
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
{
ACTIONS = {
revealFile = id;
setRatioCheck = id;
setRatioLimit = id;
setWaitToStart = id;
};
ACTIONS = {revealFile = id; setRatioCheck = id; setRatioLimit = id; };
CLASS = InfoWindowController;
LANGUAGE = ObjC;
OUTLETS = {
@ -46,7 +41,6 @@
fTrackerField = NSTextField;
fUploadedTotalField = NSTextField;
fUploadingToField = NSTextField;
fWaitToStartButton = NSButton;
};
SUPERCLASS = NSWindowController;
},

View File

@ -15,6 +15,7 @@
removeNoDelete = id;
resumeAllTorrents = id;
resumeSelectedTorrents = id;
resumeSelectedTorrentsNoWait = id;
resumeWaitingTorrents = id;
revealFile = id;
setFilter = id;
@ -27,6 +28,7 @@
showInfo = id;
showMainWindow = id;
showMessageWindow = id;
showPiecesView = id;
showPreferenceWindow = id;
stopAllTorrents = id;
stopSelectedTorrents = id;

View File

@ -7,15 +7,15 @@
<key>IBEditorPositions</key>
<dict>
<key>1041</key>
<string>439 418 208 130 0 0 1152 842 </string>
<string>379 362 208 130 0 0 1024 746 </string>
<key>1480</key>
<string>366 546 420 63 0 0 1152 842 </string>
<key>1603</key>
<string>337 545 477 67 0 0 1152 842 </string>
<key>29</key>
<string>104 684 451 44 0 0 1152 842 </string>
<string>280 699 451 44 0 0 1152 842 </string>
<key>456</key>
<string>396 374 216 206 0 0 1152 842 </string>
<string>340 316 240 225 0 0 1024 746 </string>
<key>581</key>
<string>571 464 115 99 0 0 1152 842 </string>
<key>589</key>
@ -31,8 +31,8 @@
<integer>3</integer>
<key>IBOpenObjects</key>
<array>
<integer>21</integer>
<integer>29</integer>
<integer>21</integer>
</array>
<key>IBSystem Version</key>
<string>8J135</string>

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
IBClasses = (
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
{
CLASS = PiecesWindowController;
LANGUAGE = ObjC;
OUTLETS = {fImageView = NSImageView; };
SUPERCLASS = NSWindowController;
}
);
IBVersion = 1;
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBDocumentLocation</key>
<string>82 71 356 240 0 0 1152 842 </string>
<key>IBFramework Version</key>
<string>446.1</string>
<key>IBLockedObjects</key>
<array/>
<key>IBOpenObjects</key>
<array>
<integer>5</integer>
</array>
<key>IBSystem Version</key>
<string>8J135</string>
</dict>
</plist>

Binary file not shown.

View File

@ -17,16 +17,18 @@
setLimit = id;
setLimitCheck = id;
setMoveTorrent = id;
setNat = id;
setPlaySound = id;
setPort = id;
setQueueNumber = id;
setRatio = id;
setRatioCheck = id;
setShowMessage = id;
setSound = id;
setSpeedLimit = id;
setStartNumber = id;
setStartSetting = id;
setStartAtOpen = id;
setUpdate = id;
setUseQueue = id;
};
CLASS = PrefsController;
LANGUAGE = ObjC;
@ -44,10 +46,15 @@
fFolderPopUp = NSPopUpButton;
fGeneralView = NSView;
fImportFolderPopUp = NSPopUpButton;
fNatCheck = NSButton;
fNatStatusField = NSTextField;
fNatStatusImage = NSImageView;
fNetworkView = NSView;
fPlayDownloadSoundCheck = NSButton;
fPlaySeedingSoundCheck = NSButton;
fPortField = NSTextField;
fQueueCheck = NSButton;
fQueueNumberField = NSTextField;
fQuitCheck = NSButton;
fQuitDownloadingCheck = NSButton;
fRatioCheck = NSButton;
@ -60,8 +67,7 @@
fSpeedLimitAutoOnField = NSTextField;
fSpeedLimitDownloadField = NSTextField;
fSpeedLimitUploadField = NSTextField;
fStartMatrix = NSMatrix;
fStartNumberField = NSTextField;
fStartAtOpenCheck = NSButton;
fTransfersView = NSView;
fUpdatePopUp = NSPopUpButton;
fUpdater = SUUpdater;

View File

@ -3,17 +3,17 @@
<plist version="1.0">
<dict>
<key>IBDocumentLocation</key>
<string>49 68 356 240 0 0 1152 842 </string>
<string>15 73 356 240 0 0 1024 746 </string>
<key>IBEditorPositions</key>
<dict>
<key>153</key>
<string>285 423 582 311 0 0 1152 842 </string>
<string>30 315 577 267 0 0 1024 746 </string>
<key>28</key>
<string>58 372 582 290 0 0 1152 842 </string>
<string>22 331 577 290 0 0 1024 746 </string>
<key>41</key>
<string>285 427 582 304 0 0 1152 842 </string>
<string>230 355 563 317 0 0 1024 746 </string>
<key>66</key>
<string>164 527 582 104 0 0 1152 842 </string>
<string>29 426 563 159 0 0 1024 746 </string>
</dict>
<key>IBFramework Version</key>
<string>446.1</string>
@ -23,7 +23,7 @@
</array>
<key>IBOpenObjects</key>
<array>
<integer>153</integer>
<integer>41</integer>
</array>
<key>IBSystem Version</key>
<string>8J135</string>

BIN
macosx/Images/BoxBlue1.tiff Normal file

Binary file not shown.

BIN
macosx/Images/BoxBlue2.tiff Normal file

Binary file not shown.

BIN
macosx/Images/BoxBlue3.tiff Normal file

Binary file not shown.

BIN
macosx/Images/BoxGreen.tiff Normal file

Binary file not shown.

BIN
macosx/Images/BoxWhite.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -52,12 +52,11 @@
IBOutlet NSMatrix * fRatioMatrix;
IBOutlet NSTextField * fRatioLimitField;
IBOutlet NSButton * fWaitToStartButton;
}
- (void) updateInfoForTorrents: (NSArray *) torrents;
- (void) updateInfoStats;
- (void) updateInfoStatsAndSettings;
- (void) updateInfoSettings;
- (void) setNextTab;
- (void) setPreviousTab;
@ -66,6 +65,4 @@
- (void) setRatioCheck: (id) sender;
- (void) setRatioLimit: (id) sender;
- (void) setWaitToStart: (id) sender;
@end

View File

@ -43,7 +43,7 @@
#define TAB_ACTIVITY_HEIGHT 230.0
#define TAB_PEERS_HEIGHT 255.0
#define TAB_FILES_HEIGHT 255.0
#define TAB_OPTIONS_HEIGHT 116.0
#define TAB_OPTIONS_HEIGHT 83.0
@interface InfoWindowController (Private)
@ -209,7 +209,8 @@
}
//update stats and settings
[self updateInfoStatsAndSettings];
[self updateInfoStats];
[self updateInfoSettings];
//set file table
[fFiles removeAllObjects];
@ -282,36 +283,15 @@
}
}
- (void) updateInfoStatsAndSettings
- (void) updateInfoSettings
{
int numberSelected = [fTorrents count];
//set wait to start
BOOL waiting = NO, notWaiting = NO, canEnableWaiting = numberSelected > 0,
waitingSettingOn = [[[NSUserDefaults standardUserDefaults] stringForKey: @"StartSetting"]
isEqualToString: @"Wait"];
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
while ((torrent = [enumerator nextObject]))
{
if ([torrent waitingToStart])
waiting = YES;
else
notWaiting = YES;
if (canEnableWaiting && !(![torrent isActive] && [torrent progress] < 1.0 && waitingSettingOn))
canEnableWaiting = NO;
}
[fWaitToStartButton setState: waiting && notWaiting ? NSMixedState : (waiting ? NSOnState : NSOffState)];
[fWaitToStartButton setEnabled: canEnableWaiting];
//set ratio settings
if (numberSelected > 0)
{
enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent = [enumerator nextObject]; //first torrent
const int INVALID = -99;
int ratioSetting = [torrent stopRatioSetting];
float ratioLimit = [torrent ratioLimit];
@ -592,16 +572,4 @@
}
}
- (void) setWaitToStart: (id) sender
{
int state = [sender state];
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
while ((torrent = [enumerator nextObject]))
[torrent setWaitToStart: state];
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStartSettingChange" object: fTorrents];
}
@end

View File

@ -30,7 +30,7 @@
#define LEVEL_DEBUG 2
#define UPDATE_SECONDS 0.6
#define MAX_LINES 1000
#define MAX_LINES 2500
@interface MessageWindowController (Private)

View File

@ -50,13 +50,17 @@
IBOutlet NSButton * fUploadCheck, * fDownloadCheck,
* fSpeedLimitAutoCheck;
IBOutlet NSTextField * fPortField;
IBOutlet NSTextField * fPortField, * fNatStatusField;
IBOutlet NSButton * fNatCheck;
IBOutlet NSImageView * fNatStatusImage;
NSTimer * fNatStatusTimer;
int fNatStatus;
IBOutlet NSButton * fRatioCheck;
IBOutlet NSTextField * fRatioField;
IBOutlet NSMatrix * fStartMatrix;
IBOutlet NSTextField * fStartNumberField;
IBOutlet NSButton * fQueueCheck, * fStartAtOpenCheck;
IBOutlet NSTextField * fQueueNumberField;
IBOutlet SUUpdater * fUpdater;
@ -73,14 +77,19 @@
- (void) setUpdate: (id) sender;
- (void) checkUpdate;
- (void) setStartSetting: (id) sender;
- (void) setStartNumber: (id) sender;
- (void) setStartAtOpen: (id) sender;
- (void) setUseQueue: (id) sender;
- (void) setQueueNumber: (id) sender;
- (void) setMoveTorrent: (id) sender;
- (void) setDownloadLocation: (id) sender;
- (void) folderSheetShow: (id) sender;
- (void) setPort: (id) sender;
- (void) setPort: (id) sender;
- (void) setNat: (id) sender;
- (void) updateNatStatus;
- (void) setSpeedLimit: (id) sender;
- (void) setAutoSpeedLimitCheck: (id) sender;

View File

@ -33,10 +33,6 @@
#define DOWNLOAD_TORRENT 2
#define DOWNLOAD_ASK 3
#define START_YES_CHECK_TAG 0
#define START_WAIT_CHECK_TAG 1
#define START_NO_CHECK_TAG 2
#define UPDATE_DAILY 0
#define UPDATE_WEEKLY 1
#define UPDATE_NEVER 2
@ -79,6 +75,8 @@
- (void) dealloc
{
[fNatStatusTimer invalidate];
[fDownloadFolder release];
[fImportFolder release];
[super dealloc];
@ -126,6 +124,17 @@
[fPortField setIntValue: bindPort];
tr_setBindPort(fHandle, bindPort);
//set NAT
BOOL natShouldEnable = [fDefaults boolForKey: @"NatTraversal"];
if (natShouldEnable)
tr_natTraversalEnable(fHandle);
[fNatCheck setState: natShouldEnable];
fNatStatus = -1;
[self updateNatStatus];
fNatStatusTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0 target: self
selector: @selector(updateNatStatus) userInfo: nil repeats: YES];
//checks for old version upload speed of -1
if ([fDefaults integerForKey: @"UploadLimit"] < 0)
{
@ -249,19 +258,13 @@
else
[fDefaults setObject: [fSeedingSoundPopUp titleOfSelectedItem] forKey: @"SeedingSound"];
//set start setting
NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
int tag;
if ([startSetting isEqualToString: @"Start"])
tag = START_YES_CHECK_TAG;
else if ([startSetting isEqualToString: @"Wait"])
tag = START_WAIT_CHECK_TAG;
else
tag = START_NO_CHECK_TAG;
//set start settings
BOOL useQueue = [fDefaults boolForKey: @"Queue"];
[fQueueCheck setState: useQueue];
[fQueueNumberField setEnabled: useQueue];
[fQueueNumberField setIntValue: [fDefaults integerForKey: @"QueueDownloadNumber"]];
[fStartMatrix selectCellWithTag: tag];
[fStartNumberField setEnabled: tag == START_WAIT_CHECK_TAG];
[fStartNumberField setIntValue: [fDefaults integerForKey: @"WaitToStartNumber"]];
[fStartAtOpenCheck setState: [fDefaults boolForKey: @"StartAtOpen"]];
//set private torrents
BOOL copyTorrents = [fDefaults boolForKey: @"SavePrivateTorrent"];
@ -354,6 +357,41 @@
{
tr_setBindPort(fHandle, bindPort);
[fDefaults setInteger: bindPort forKey: @"BindPort"];
[self updateNatStatus];
}
}
- (void) setNat: (id) sender
{
BOOL enable = [sender state] == NSOnState;
enable ? tr_natTraversalEnable(fHandle) : tr_natTraversalDisable(fHandle);
[fDefaults setBool: enable forKey: @"NatTraversal"];
[self updateNatStatus];
}
- (void) updateNatStatus
{
int status = tr_natTraversalStatus(fHandle);
if (fNatStatus == status)
return;
fNatStatus = status;
if (status == 2)
{
[fNatStatusField setStringValue: @"Ports successfully mapped"];
[fNatStatusImage setImage: [NSImage imageNamed: @"Check.png"]];
}
else if (status == 3 || status == 4)
{
[fNatStatusField setStringValue: @"Error mapping ports"];
[fNatStatusImage setImage: [NSImage imageNamed: @"Error.tiff"]];
}
else
{
[fNatStatusField setStringValue: @""];
[fNatStatusImage setImage: nil];
}
}
@ -661,35 +699,31 @@
[fUpdater scheduleCheckWithInterval: seconds];
}
- (void) setStartSetting: (id) sender
- (void) setStartAtOpen: (id) sender
{
NSString * startSetting;
int tag = [[fStartMatrix selectedCell] tag];
if (tag == START_YES_CHECK_TAG)
startSetting = @"Start";
else if (tag == START_WAIT_CHECK_TAG)
startSetting = @"Wait";
else
startSetting = @"Manual";
[fDefaults setObject: startSetting forKey: @"StartSetting"];
[self setStartNumber: fStartNumberField];
[fStartNumberField setEnabled: tag == START_WAIT_CHECK_TAG];
[fDefaults setBool: [sender state] == NSOnState forKey: @"StartAtOpen"];
}
- (void) setStartNumber: (id) sender
- (void) setUseQueue: (id) sender
{
int waitNumber = [sender intValue];
if (![[sender stringValue] isEqualToString: [NSString stringWithInt: waitNumber]] || waitNumber < 1)
BOOL useQueue = [sender state] == NSOnState;
[fDefaults setBool: useQueue forKey: @"Queue"];
[self setQueueNumber: fQueueNumberField];
[fQueueNumberField setEnabled: useQueue];
}
- (void) setQueueNumber: (id) sender
{
int queueNumber = [sender intValue];
if (![[sender stringValue] isEqualToString: [NSString stringWithInt: queueNumber]] || queueNumber < 1)
{
NSBeep();
waitNumber = [fDefaults floatForKey: @"WaitToStartNumber"];
[sender setIntValue: waitNumber];
queueNumber = [fDefaults integerForKey: @"QueueDownloadNumber"];
[sender setIntValue: queueNumber];
}
else
[fDefaults setInteger: waitNumber forKey: @"WaitToStartNumber"];
[fDefaults setInteger: queueNumber forKey: @"QueueDownloadNumber"];
[[NSNotificationCenter defaultCenter] postNotificationName: @"GlobalStartSettingChange" object: self];
}

View File

@ -51,7 +51,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
kBlue2 = BE(0x78BEFFFF), //120, 190, 255
kBlue3 = BE(0x50A0FFFF), //80, 160, 255
kBlue4 = BE(0x1E46B4FF), //30, 70, 180
kGray = BE(0x828282FF), //130, 130, 130
kGray = BE(0x969696FF), //150, 150, 150
kGreen = BE(0x00FF00FF), //0, 255, 0
kWhite = BE(0xFFFFFFFF); //255, 255, 255
@ -192,7 +192,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
case TR_STATUS_PAUSE:
if (fFinishedSeeding)
tempString = @"Seeding complete";
else if (fWaitToStart && [[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
else if (fWaitToStart)
tempString = [@"Waiting to start" stringByAppendingEllipsis];
else
tempString = @"Paused";
@ -315,83 +315,6 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return info;
}
- (NSImage *) advancedBar
{
#warning figure out length
int width = 250; //integers for bars
NSBitmapImageRep * bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
pixelsWide: width pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
int h, w;
uint32_t * p;
uint8_t * bitmapData = [bitmap bitmapData];
int bytesPerRow = [bitmap bytesPerRow];
int8_t * pieces = malloc(width);
[self getAvailability: pieces size: width];
int avail = 0;
for (w = 0; w < width; w++)
if (pieces[w] != 0)
avail++;
//first two lines: dark blue to show progression, green to show available
int end = lrintf(floor([self progress] * width));
p = (uint32_t *) bitmapData;
for (w = 0; w < end; w++)
{
p[w] = kBlue4;
p[w + bytesPerRow / 4] = kBlue4;
}
for (; w < avail; w++)
{
p[w] = kGreen;
p[w + bytesPerRow / 4] = kGreen;
}
for (; w < width - 2; w++)
{
p[w] = kWhite;
p[w + bytesPerRow / 4] = kWhite;
}
//lines 2 to 14: blue or grey depending on whether we have the piece or not
uint32_t color;
for( w = 0; w < width; w++ )
{
if (pieces[w] < 0)
color = kGray;
else if (pieces[w] == 0)
color = kRed;
else if (pieces[w] == 1)
color = kBlue1;
else if (pieces[w] == 2)
color = kBlue2;
else
color = kBlue3;
//point to pixel (w, 2) and draw "vertically"
p = (uint32_t *) ( bitmapData + 2 * bytesPerRow ) + w;
for( h = 2; h < BAR_HEIGHT; h++ )
{
p[0] = color;
p = (uint32_t *) ( (uint8_t *) p + bytesPerRow );
}
}
free(pieces);
//actually draw image
NSImage * bar = [[NSImage alloc] initWithSize: [bitmap size]];
[bar addRepresentation: bitmap];
[bitmap release];
[bar setScalesWhenResized: YES];
return [bar autorelease];
}
- (void) startTransfer
{
if (![self isActive])
@ -400,8 +323,6 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
fFinishedSeeding = NO;
fWaitToStart = NO;
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentSettingChange" object: self];
}
}
@ -835,8 +756,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
fFinishedSeeding = NO;
fWaitToStart = waitToStart ? [waitToStart boolValue]
: ![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Manual"];
fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"StartAtOpen"];
fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
@ -859,6 +779,82 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return self;
}
- (NSImage *) advancedBar
{
int width = 250; //integers for bars
NSBitmapImageRep * bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
pixelsWide: width pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
int h, w;
uint32_t * p;
uint8_t * bitmapData = [bitmap bitmapData];
int bytesPerRow = [bitmap bytesPerRow];
int8_t * pieces = malloc(width);
[self getAvailability: pieces size: width];
int avail = 0;
for (w = 0; w < width; w++)
if (pieces[w] != 0)
avail++;
//first two lines: dark blue to show progression, green to show available
int end = lrintf(floor([self progress] * width));
p = (uint32_t *) bitmapData;
for (w = 0; w < end; w++)
{
p[w] = kBlue4;
p[w + bytesPerRow / 4] = kBlue4;
}
for (; w < avail; w++)
{
p[w] = kGreen;
p[w + bytesPerRow / 4] = kGreen;
}
for (; w < width; w++)
{
p[w] = kWhite;
p[w + bytesPerRow / 4] = kWhite;
}
//lines 2 to 14: blue or grey depending on whether we have the piece or not
uint32_t color;
for( w = 0; w < width; w++ )
{
if (pieces[w] < 0)
color = kGreen;
else if (pieces[w] == 0)
color = kGray;
else if (pieces[w] == 1)
color = kBlue1;
else if (pieces[w] == 2)
color = kBlue2;
else
color = kBlue3;
//point to pixel (w, 2) and draw "vertically"
p = (uint32_t *) ( bitmapData + 2 * bytesPerRow ) + w;
for( h = 2; h < BAR_HEIGHT; h++ )
{
p[0] = color;
p = (uint32_t *) ( (uint8_t *) p + bytesPerRow );
}
}
free(pieces);
//actually draw image
NSImage * bar = [[NSImage alloc] initWithSize: [bitmap size]];
[bar addRepresentation: bitmap];
[bitmap release];
[bar setScalesWhenResized: YES];
return [bar autorelease];
}
- (void) trashFile: (NSString *) path
{
//attempt to move to trash

View File

@ -151,13 +151,6 @@
{
NSDictionary * info = [self objectValue];
//if seeding, there's no need for the advanced bar
if ([[info objectForKey: @"Seeding"] boolValue])
{
[self buildSimpleBar: widthFloat point: point];
return;
}
//draw overlay over advanced bar
[fProgressEndAdvanced compositeToPoint: point operation: NSCompositeSourceOver];

View File

@ -39,7 +39,9 @@
NSUserDefaults * fDefaults;
IBOutlet NSMenu * fContextRow, * fContextNoRow;
NSImage * fResumeOnIcon, * fResumeOffIcon, * fPauseOnIcon, * fPauseOffIcon,
NSImage * fResumeOnIcon, * fResumeOffIcon,
* fPauseOnIcon, * fPauseOffIcon,
* fResumeNoWaitOnIcon, * fResumeNoWaitOffIcon,
* fRevealOnIcon, * fRevealOffIcon;
NSMutableArray * fKeyStrokes;

View File

@ -55,6 +55,8 @@
fResumeOnIcon = [NSImage imageNamed: @"ResumeOn.png"];
fResumeOffIcon = [NSImage imageNamed: @"ResumeOff.png"];
fPauseOnIcon = [NSImage imageNamed: @"PauseOn.png"];
fResumeNoWaitOnIcon = [NSImage imageNamed: @"ResumeNoWaitOn.png"];
fResumeNoWaitOffIcon = [NSImage imageNamed: @"ResumeNoWaitOff.png"];
fPauseOffIcon = [NSImage imageNamed: @"PauseOff.png"];
fRevealOnIcon = [NSImage imageNamed: @"RevealOn.png"];
fRevealOffIcon = [NSImage imageNamed: @"RevealOff.png"];
@ -94,17 +96,20 @@
{
fClickPoint = [self convertPoint: [event locationInWindow] fromView: nil];
if ([event modifierFlags] & NSAlternateKeyMask)
if (![self pointInPauseRect: fClickPoint] && ![self pointInRevealRect: fClickPoint])
{
[fController toggleAdvancedBar: self];
fClickPoint = NSZeroPoint;
}
else if (![self pointInPauseRect: fClickPoint] && ![self pointInRevealRect: fClickPoint])
{
if ([self pointInMinimalStatusRect: fClickPoint])
[(TorrentCell *)[[self tableColumnWithIdentifier: @"Torrent"] dataCell] toggleMinimalStatus];
if ([event modifierFlags] & NSAlternateKeyMask)
{
[fController toggleAdvancedBar: self];
fClickPoint = NSZeroPoint;
}
else
{
if ([self pointInMinimalStatusRect: fClickPoint])
[(TorrentCell *)[[self tableColumnWithIdentifier: @"Torrent"] dataCell] toggleMinimalStatus];
[super mouseDown: event];
[super mouseDown: event];
}
}
else;
@ -121,15 +126,22 @@
{
Torrent * torrent = [fTorrents objectAtIndex: row];
if ([torrent isPaused])
[fController resumeTorrents: [NSArray arrayWithObject: torrent]];
else if ([torrent isActive])
if ([torrent isActive])
[fController stopTorrents: [NSArray arrayWithObject: torrent]];
else;
else if ([torrent isPaused])
{
if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
[fController resumeTorrentsNoWait: [NSArray arrayWithObject: torrent]];
else if ([torrent waitingToStart])
[fController stopTorrents: [NSArray arrayWithObject: torrent]];
else
[fController resumeTorrents: [NSArray arrayWithObject: torrent]];
}
else;
}
else if (sameRow && [self pointInRevealRect: point] && [self pointInRevealRect: fClickPoint])
[[fTorrents objectAtIndex: row] revealData];
else if ([event clickCount] == 2)
else if ([event clickCount] == 2)
{
if ([self pointInIconRect: point])
[[fTorrents objectAtIndex: row] revealData];
@ -138,7 +150,7 @@
}
else;
[super mouseUp: event];
[super mouseUp: event];
fClickPoint = NSZeroPoint;
[self display];
@ -184,6 +196,12 @@
}
}
- (void) flagsChanged: (NSEvent *) event
{
[self display];
[super flagsChanged: event];
}
- (void) insertText: (NSString *) text
{
//sort torrents by name before finding closest match
@ -223,10 +241,17 @@
torrent = [fTorrents objectAtIndex: i];
rect = [self pauseRectForRow: i];
if ([torrent isPaused])
image = NSPointInRect(fClickPoint, rect) ? fResumeOnIcon : fResumeOffIcon;
else if ([torrent isActive])
if ([torrent isActive])
image = NSPointInRect(fClickPoint, rect) ? fPauseOnIcon : fPauseOffIcon;
else if ([torrent isPaused])
{
if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask && [fDefaults boolForKey: @"Queue"])
image = NSPointInRect(fClickPoint, rect) ? fResumeNoWaitOnIcon : fResumeNoWaitOffIcon;
else if ([torrent waitingToStart])
image = NSPointInRect(fClickPoint, rect) ? fPauseOnIcon : fPauseOffIcon;
else
image = NSPointInRect(fClickPoint, rect) ? fResumeOnIcon : fResumeOffIcon;
}
else
image = nil;

View File

@ -1,6 +1,6 @@
# $Id$
TMPCFLAGS = -g -Wall -W -O3 -funroll-loops -D_FILE_OFFSET_BITS=64 \
TMPCFLAGS = -g -Wall -W -D_FILE_OFFSET_BITS=64 \
-D_LARGEFILE_SOURCE -D_GNU_SOURCE \
-DSYS_$(shell echo $(SYSTEM) | tr a-z A-Z)
TMPCXXFLAGS = $(TMPCFLAGS)

View File

@ -5,7 +5,7 @@ include ../mk/common.mk
SRCS = transmission.c bencode.c net.c tracker.c peer.c inout.c \
metainfo.c sha1.c utils.c fdlimit.c clients.c completion.c \
platform.c ratecontrol.c choking.c
platform.c ratecontrol.c choking.c natpmp.c upnp.c http.c xml.c
OBJS = $(SRCS:%.c=%.o)
CFLAGS += -D__TRANSMISSION__