Merge file selection and torrent creation into the main branch.

The new code for these features is under a new license.
This commit is contained in:
Mitchell Livingston 2007-06-18 03:40:41 +00:00
parent 33ade6978b
commit 0256f4616d
102 changed files with 5259 additions and 3645 deletions

14
LICENSE
View File

@ -1,5 +1,15 @@
The Transmission binaries and source code are distributed under the MIT
license.
The Transmission binaries and most of its source code is
distributed under the MIT license.
Some files are copyrighted by Charles Kerr and are covered by
the GPL version 2. Works owned by the Transmission project
are granted a special exemption to clause 2(b) so that the bulk
of its code can remain under the MIT license. This exemption does
not extend to original or derived works not owned by the
Transmission project.
The remainder of this text pertains only to the MIT-licensed
portions of Transmission.
-----
Permission is hereby granted, free of charge, to any person obtaining

17
NEWS
View File

@ -1,5 +1,22 @@
NEWS file for Transmission <http://transmission.m0k.org/>
0.80 ()
- Ability to selectively download and prioritize files
- Torrent file creation
- Partial licensing change -- see the LICENSE file for details
- Fix to UPnP
- Rechecking torrents is now done one torrent at a time
to avoid hammering the disk
- Many miscellaneous bugfixes and small improvements
- OS X:
+ Overlay when dragging torrent files onto window
+ Ability to set an amount of time to considered a transfer stalled
+ Various smaller interface changes and improvements
- GTK:
+ Interface significantly reworked to closer match the Mac version
+ Torrent inspector
+ Other minor improvements
0.72 (2007/04/30)
- Reset download/upload amounts when sending "started"
- Fix rare XML parsing bug

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
002C9EE60C19CD2500C2F6F4 /* fastresume.c in Sources */ = {isa = PBXBuildFile; fileRef = 002C9EE50C19CD2500C2F6F4 /* fastresume.c */; };
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 */; };
@ -104,11 +105,17 @@
A2A306620AAD24A80049E2AC /* UKMainThreadProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = A2A3065A0AAD24A80049E2AC /* UKMainThreadProxy.m */; };
A2AA579D0ADFCAB400CA59F6 /* PiecesImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = A2AA579B0ADFCAB400CA59F6 /* PiecesImageView.m */; };
A2AF1C390A3D0F6200F1575D /* FileOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = A2AF1C370A3D0F6200F1575D /* FileOutlineView.m */; };
A2BE9C520C1E4AF5002D16E6 /* makemeta.c in Sources */ = {isa = PBXBuildFile; fileRef = A2BE9C4E0C1E4ADA002D16E6 /* makemeta.c */; };
A2BE9C530C1E4AF7002D16E6 /* makemeta.h in Headers */ = {isa = PBXBuildFile; fileRef = A2BE9C4F0C1E4ADA002D16E6 /* makemeta.h */; };
A2BF07910B066E0800757C92 /* SpeedLimitToTurtleIconTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = A2BF078F0B066E0800757C92 /* SpeedLimitToTurtleIconTransformer.m */; };
A2C655650A04FEDC00E9FD82 /* BottomBorder.png in Resources */ = {isa = PBXBuildFile; fileRef = A2C655640A04FEDC00E9FD82 /* BottomBorder.png */; };
A2D0E0490A54A97C003C72CF /* Bandwidth.png in Resources */ = {isa = PBXBuildFile; fileRef = A2D0E0480A54A97C003C72CF /* Bandwidth.png */; };
A2D4F0830A915F6600890C32 /* RedDot.tiff in Resources */ = {isa = PBXBuildFile; fileRef = A2D4F0820A915F6600890C32 /* RedDot.tiff */; };
A2D4F0850A915F7200890C32 /* GreenDot.tiff in Resources */ = {isa = PBXBuildFile; fileRef = A2D4F0840A915F7200890C32 /* GreenDot.tiff */; };
A2DF37060C220D03006523C1 /* CreatorWindowController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A2DF37040C220D03006523C1 /* CreatorWindowController.h */; };
A2DF37070C220D03006523C1 /* CreatorWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A2DF37050C220D03006523C1 /* CreatorWindowController.m */; };
A2DF377C0C222E2D006523C1 /* Creator.nib in Resources */ = {isa = PBXBuildFile; fileRef = A2DF377A0C222E2D006523C1 /* Creator.nib */; };
A2E9AA760C249AF400085DCF /* Create.png in Resources */ = {isa = PBXBuildFile; fileRef = A2E9AA750C249AF400085DCF /* Create.png */; };
A2F40AE40A361C00006B8288 /* Transmission.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D2784360905709500687951 /* Transmission.icns */; };
A2F6DB090A55F31C0058D1E5 /* SpeedLimitButton.png in Resources */ = {isa = PBXBuildFile; fileRef = A2F6DB070A55F31C0058D1E5 /* SpeedLimitButton.png */; };
A2FB057F0BFEB6800095564D /* DragOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = A2FB057D0BFEB6800095564D /* DragOverlayView.m */; };
@ -242,12 +249,14 @@
files = (
A261F1E40A69A1B10002815A /* Growl.framework in CopyFiles */,
A24F19210A3A796800C9C145 /* Sparkle.framework in CopyFiles */,
A2DF37060C220D03006523C1 /* CreatorWindowController.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
002C9EE50C19CD2500C2F6F4 /* fastresume.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fastresume.c; path = libtransmission/fastresume.c; sourceTree = "<group>"; };
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = macosx/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
@ -303,7 +312,7 @@
4DFBC2DD09C0970D00D5C571 /* Torrent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Torrent.h; path = macosx/Torrent.h; sourceTree = "<group>"; };
4DFBC2DE09C0970D00D5C571 /* Torrent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Torrent.m; path = macosx/Torrent.m; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; name = Info.plist; path = macosx/Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* Transmission.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transmission.app; sourceTree = BUILT_PRODUCTS_DIR; };
8D1107320486CEB800E47090 /* Transmission.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Transmission.app; sourceTree = BUILT_PRODUCTS_DIR; };
A200B8390A2263BA007BBB1E /* InfoWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = InfoWindowController.h; path = macosx/InfoWindowController.h; sourceTree = "<group>"; };
A200B83A0A2263BA007BBB1E /* InfoWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = InfoWindowController.m; path = macosx/InfoWindowController.m; sourceTree = "<group>"; };
A200B9630A227FD0007BBB1E /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = macosx/English.lproj/InfoWindow.nib; sourceTree = "<group>"; };
@ -406,12 +415,18 @@
A2AA579B0ADFCAB400CA59F6 /* PiecesImageView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = PiecesImageView.m; path = macosx/PiecesImageView.m; sourceTree = "<group>"; };
A2AF1C360A3D0F6200F1575D /* FileOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = FileOutlineView.h; path = macosx/FileOutlineView.h; sourceTree = "<group>"; };
A2AF1C370A3D0F6200F1575D /* FileOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = FileOutlineView.m; path = macosx/FileOutlineView.m; sourceTree = "<group>"; };
A2BE9C4E0C1E4ADA002D16E6 /* makemeta.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = makemeta.c; path = libtransmission/makemeta.c; sourceTree = "<group>"; };
A2BE9C4F0C1E4ADA002D16E6 /* makemeta.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = makemeta.h; path = libtransmission/makemeta.h; sourceTree = "<group>"; };
A2BF078E0B066E0800757C92 /* SpeedLimitToTurtleIconTransformer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = SpeedLimitToTurtleIconTransformer.h; path = macosx/SpeedLimitToTurtleIconTransformer.h; sourceTree = "<group>"; };
A2BF078F0B066E0800757C92 /* SpeedLimitToTurtleIconTransformer.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = SpeedLimitToTurtleIconTransformer.m; path = macosx/SpeedLimitToTurtleIconTransformer.m; sourceTree = "<group>"; };
A2C655640A04FEDC00E9FD82 /* BottomBorder.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = BottomBorder.png; path = macosx/Images/BottomBorder.png; sourceTree = "<group>"; };
A2D0E0480A54A97C003C72CF /* Bandwidth.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Bandwidth.png; path = macosx/Images/Bandwidth.png; sourceTree = "<group>"; };
A2D4F0820A915F6600890C32 /* RedDot.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = RedDot.tiff; path = macosx/Images/RedDot.tiff; sourceTree = "<group>"; };
A2D4F0840A915F7200890C32 /* GreenDot.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = GreenDot.tiff; path = macosx/Images/GreenDot.tiff; sourceTree = "<group>"; };
A2DF37040C220D03006523C1 /* CreatorWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CreatorWindowController.h; path = macosx/CreatorWindowController.h; sourceTree = "<group>"; };
A2DF37050C220D03006523C1 /* CreatorWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CreatorWindowController.m; path = macosx/CreatorWindowController.m; sourceTree = "<group>"; };
A2DF377B0C222E2D006523C1 /* Creator.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Creator.nib; path = macosx/English.lproj/Creator.nib; sourceTree = "<group>"; };
A2E9AA750C249AF400085DCF /* Create.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Create.png; path = macosx/Images/Create.png; sourceTree = "<group>"; };
A2F6DB070A55F31C0058D1E5 /* SpeedLimitButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = SpeedLimitButton.png; path = macosx/Images/SpeedLimitButton.png; sourceTree = "<group>"; };
A2F8951E0A2D4BA500ED2127 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Credits.rtf; path = macosx/Credits.rtf; sourceTree = "<group>"; };
A2FB057C0BFEB6800095564D /* DragOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = DragOverlayView.h; path = macosx/DragOverlayView.h; sourceTree = "<group>"; };
@ -591,6 +606,8 @@
A21576090C0D449A0057A26A /* BezierPathAdditions.m */,
A256588A0A9A695400E8A03B /* MessageWindowController.h */,
A256588B0A9A695400E8A03B /* MessageWindowController.m */,
A2DF37040C220D03006523C1 /* CreatorWindowController.h */,
A2DF37050C220D03006523C1 /* CreatorWindowController.m */,
E1B6FBF80C0D719B0015FE4D /* Info Window */,
E1B6FBFD0C0D72430015FE4D /* Prefs Window */,
E1B6FC000C0D72A00015FE4D /* Overlay Window */,
@ -646,6 +663,7 @@
4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */,
4DF7500808A103AD007B0D70 /* Info.png */,
4DF7500708A103AD007B0D70 /* Open.png */,
A2E9AA750C249AF400085DCF /* Create.png */,
4DF7500908A103AD007B0D70 /* Remove.png */,
35B037F90AC5B53800A10FDF /* ResumeNoWaitOn.png */,
35B037FA0AC5B53800A10FDF /* ResumeNoWaitOff.png */,
@ -670,6 +688,7 @@
29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
A200B9620A227FD0007BBB1E /* InfoWindow.nib */,
A21567EB0A9A5034004DECD6 /* MessageWindow.nib */,
A2DF377A0C222E2D006523C1 /* Creator.nib */,
A2C655640A04FEDC00E9FD82 /* BottomBorder.png */,
A289EB0B0A33C56D00B082A3 /* ButtonBorder.png */,
A28DBADB0A33C1D800F4B4A7 /* ActionButton.png */,
@ -713,6 +732,9 @@
4D1838DC09DEC04A0047D688 /* libtransmission */ = {
isa = PBXGroup;
children = (
A2BE9C4E0C1E4ADA002D16E6 /* makemeta.c */,
A2BE9C4F0C1E4ADA002D16E6 /* makemeta.h */,
002C9EE50C19CD2500C2F6F4 /* fastresume.c */,
A24D2A770C0A65C400A0ED9F /* ipcparse.c */,
A24D2A780C0A65C400A0ED9F /* ipcparse.h */,
BEFC1DEE0C07861A00B0BB3C /* xml.h */,
@ -938,6 +960,7 @@
BEFC1E5C0C07861A00B0BB3C /* bencode.h in Headers */,
BEFC1E5E0C07861A00B0BB3C /* bsdqueue.h in Headers */,
BEFC1E5F0C07861A00B0BB3C /* trcompat.h in Headers */,
A2BE9C530C1E4AF7002D16E6 /* makemeta.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1144,6 +1167,8 @@
A22A8D560AEEAFA5007E9CB9 /* Localizable.strings in Resources */,
A24103070AF80E390001C8D0 /* FilterButtonPressedLeft.png in Resources */,
A241528B0C0261B8007DD3B4 /* Globe.tiff in Resources */,
A2DF377C0C222E2D006523C1 /* Creator.nib in Resources */,
A2E9AA760C249AF400085DCF /* Create.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1201,6 +1226,8 @@
BEFC1E580C07861A00B0BB3C /* clients.c in Sources */,
BEFC1E5A0C07861A00B0BB3C /* choking.c in Sources */,
BEFC1E5D0C07861A00B0BB3C /* bencode.c in Sources */,
002C9EE60C19CD2500C2F6F4 /* fastresume.c in Sources */,
A2BE9C520C1E4AF5002D16E6 /* makemeta.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1252,6 +1279,7 @@
A24D2A640C0A624600A0ED9F /* IPCController.m in Sources */,
A24838820C0BA608005CC3FE /* FilterBarView.m in Sources */,
A215760B0C0D449A0057A26A /* BezierPathAdditions.m in Sources */,
A2DF37070C220D03006523C1 /* CreatorWindowController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1392,12 +1420,24 @@
name = PrefsWindow.nib;
sourceTree = "<group>";
};
A2DF377A0C222E2D006523C1 /* Creator.nib */ = {
isa = PBXVariantGroup;
children = (
A2DF377B0C222E2D006523C1 /* Creator.nib */,
);
name = Creator.nib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
4D18389C09DEC01E0047D688 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build/Debug";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -1421,6 +1461,10 @@
4DDBB71E09E16BF100284745 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build/Debug";
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/build/Debug";
OTHER_CFLAGS = (
@ -1438,6 +1482,10 @@
4DF0C599089918A300DD8943 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/macosx";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -1473,6 +1521,10 @@
GCC_VERSION_ppc = 4.0;
GCC_WARN_ABOUT_RETURN_TYPE = NO;
GCC_WARN_SIGN_COMPARE = NO;
GCC_WARN_UNUSED_FUNCTION = NO;
GCC_WARN_UNUSED_LABEL = NO;
GCC_WARN_UNUSED_PARAMETER = NO;
GCC_WARN_UNUSED_VALUE = NO;
GCC_WARN_UNUSED_VARIABLE = NO;
MACOSX_DEPLOYMENT_TARGET_i386 = 10.4;
MACOSX_DEPLOYMENT_TARGET_ppc = 10.4;
@ -1486,6 +1538,10 @@
BEFC1C0A0C07753800B0BB3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build/Debug";
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
@ -1517,6 +1573,10 @@
BEFC1CF80C07822400B0BB3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build/Debug";
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
@ -1550,6 +1610,10 @@
BEFC1D420C0783EE00B0BB3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build/Debug";
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;

View File

@ -61,7 +61,7 @@ void TRTransfer::Update(BView *owner, const BFont *font) {
* Returns a bool signaling the view is dirty after the update.
*
* The view is determined to be dirty if the transfer
* status, progress, eta or the "shade" (even or odd)
* status, percentDone, eta or the "shade" (even or odd)
* position in the list changes from the previous state.
* If the tr_stat_t is in fact different then the new, full
* status is memcpy'd overtop the existing code.
@ -73,7 +73,7 @@ bool TRTransfer::UpdateStatus(tr_stat_t *stat, bool shade) {
bool dirty = false;
if (fStatusLock->Lock()) {
if (fStatus->status != stat->status ||
fStatus->progress != stat->progress ||
fStatus->percentDone != stat->percentDone ||
fStatus->eta != stat->eta ||
fShade != shade)
{
@ -124,21 +124,21 @@ void TRTransfer::DrawItem(BView *owner, BRect frame, bool) {
owner->DrawString(fName->String(), textLoc);
if (fStatus->status & TR_STATUS_PAUSE ) {
sprintf(fTimeStr, "Paused (%.2f %%)", 100 * fStatus->progress);
sprintf(fTimeStr, "Paused (%.2f %%)", 100 * fStatus->percentDone);
} else if (fStatus->status & TR_STATUS_CHECK_WAIT ) {
sprintf(fTimeStr, "Waiting To Check Existing Files (%.2f %%)",
100 * fStatus->progress);
100 * fStatus->percentDone);
} else if (fStatus->status & TR_STATUS_CHECK ) {
sprintf(fTimeStr, "Checking Existing Files (%.2f %%)",
100 * fStatus->progress);
100 * fStatus->percentDone);
} else if (fStatus->status & TR_STATUS_DOWNLOAD) {
if (fStatus->eta < 0 ) {
sprintf(fTimeStr, "--:--:-- Remaining (%.2f %%Complete)",
100 * fStatus->progress);
100 * fStatus->percentDone);
} else {
sprintf(fTimeStr, "%02d:%02d:%02d Remaining (%.2f %%Complete)",
fStatus->eta / 3600, (fStatus->eta / 60) % 60,
fStatus->eta % 60, 100 * fStatus->progress);
fStatus->eta % 60, 100 * fStatus->percentDone);
}
} else if (fStatus->status & TR_STATUS_SEED) {
sprintf(fTimeStr, "Seeding");
@ -214,8 +214,8 @@ void TRTransfer::DrawItem(BView *owner, BRect frame, bool) {
}
owner->FillRect(rect);
if (fStatus->progress != 0.0f) {
rect.right = rect.left + (float)ceil(fStatus->progress * (rect.Width() - 4)),
if (fStatus->percentDone != 0.0f) {
rect.right = rect.left + (float)ceil(fStatus->percentDone * (rect.Width() - 4)),
// Bevel
owner->SetHighColor(tint_color(fBarColor, B_LIGHTEN_2_TINT));

View File

@ -29,27 +29,40 @@
#include <getopt.h>
#include <signal.h>
#include <transmission.h>
#include <makemeta.h>
#ifdef SYS_BEOS
#include <kernel/OS.h>
#define usleep snooze
#endif
#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" \
" -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" \
" -v, --verbose <int> Verbose level (0 to 2, default = 0)\n"
/* macro to shut up "unused parameter" warnings */
#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif
const char * USAGE =
"Usage: %s [options] file.torrent [options]\n\n"
"Options:\n"
" -c, --create-from <file> Create torrent from the specified source file.\n"
" -a, --announce <url> Used in conjunction with -c.\n"
" -r, --private Used in conjunction with -c.\n"
" -m, --comment <text> Adds an optional comment when creating a torrent.\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"
" -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"
" -v, --verbose <int> Verbose level (0 to 2, default = 0)\n";
static int showHelp = 0;
static int showInfo = 0;
static int showScrape = 0;
static int isPrivate = 0;
static int verboseLevel = 0;
static int bindPort = TR_DEFAULT_PORT;
static int uploadLimit = 20;
@ -60,6 +73,9 @@ static sig_atomic_t gotsig = 0;
static tr_torrent_t * tor;
static char * finishCall = NULL;
static char * announce = NULL;
static char * sourceFile = NULL;
static char * comment = NULL;
static int parseCommandLine ( int argc, char ** argv );
static void sigHandler ( int signal );
@ -123,6 +139,20 @@ int main( int argc, char ** argv )
/* Initialize libtransmission */
h = tr_init( "cli" );
if( sourceFile && *sourceFile ) /* creating a torrent */
{
int ret;
tr_metainfo_builder_t* builder = tr_metaInfoBuilderCreate( h, sourceFile );
tr_makeMetaInfo( builder, NULL, announce, comment, isPrivate );
while( !builder->isDone ) {
usleep( 1 );
printf( "." );
}
ret = !builder->failed;
tr_metaInfoBuilderFree( builder );
return ret;
}
/* Open and parse torrent file */
if( !( tor = tr_torrentInit( h, torrentPath, NULL, 0, &error ) ) )
{
@ -223,18 +253,18 @@ int main( int argc, char ** argv )
else if( s->status & TR_STATUS_CHECK_WAIT )
{
chars = snprintf( string, sizeof string,
"Waiting to check files... %.2f %%", 100.0 * s->progress );
"Waiting to check files... %.2f %%", 100.0 * s->percentDone );
}
else if( s->status & TR_STATUS_CHECK )
{
chars = snprintf( string, sizeof string,
"Checking files... %.2f %%", 100.0 * s->progress );
"Checking files... %.2f %%", 100.0 * s->percentDone );
}
else if( s->status & TR_STATUS_DOWNLOAD )
{
chars = snprintf( string, sizeof string,
"Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), "
"ul to %d (%.2f KB/s) [%s]", 100.0 * s->progress,
"ul to %d (%.2f KB/s) [%s]", 100.0 * s->percentDone,
s->peersTotal, ( s->peersTotal == 1 ) ? "" : "s",
s->peersUploading, s->rateDownload,
s->peersDownloading, s->rateUpload,
@ -267,7 +297,7 @@ int main( int argc, char ** argv )
fprintf( stderr, "\n" );
}
if( tr_getFinished( tor ) )
if( tr_getDone(tor) || tr_getComplete(tor) )
{
result = system(finishCall);
}
@ -302,16 +332,21 @@ static int parseCommandLine( int argc, char ** argv )
{ { "help", no_argument, NULL, 'h' },
{ "info", no_argument, NULL, 'i' },
{ "scrape", no_argument, NULL, 's' },
{ "private", no_argument, NULL, 'r' },
{ "verbose", required_argument, NULL, 'v' },
{ "port", required_argument, NULL, 'p' },
{ "upload", required_argument, NULL, 'u' },
{ "download", required_argument, NULL, 'd' },
{ "finish", required_argument, NULL, 'f' },
{ "create", required_argument, NULL, 'c' },
{ "comment", required_argument, NULL, 'm' },
{ "announce", required_argument, NULL, 'a' },
{ "nat-traversal", no_argument, NULL, 'n' },
{ 0, 0, 0, 0} };
int c, optind = 0;
c = getopt_long( argc, argv, "hisv:p:u:d:f:n", long_options, &optind );
c = getopt_long( argc, argv, "hisrv:p:u:d:f:c:m:a:n:",
long_options, &optind );
if( c < 0 )
{
break;
@ -327,6 +362,9 @@ static int parseCommandLine( int argc, char ** argv )
case 's':
showScrape = 1;
break;
case 'r':
isPrivate = 1;
break;
case 'v':
verboseLevel = atoi( optarg );
break;
@ -342,6 +380,15 @@ static int parseCommandLine( int argc, char ** argv )
case 'f':
finishCall = optarg;
break;
case 'c':
sourceFile = optarg;
break;
case 'm':
comment = optarg;
break;
case 'a':
announce = optarg;
break;
case 'n':
natTraversal = 1;
break;

View File

@ -754,17 +754,14 @@ int
addstat( benc_val_t * list, int id, int types )
{
tr_stat_t * st;
tr_info_t * inf;
st = torrent_stat( id );
if( NULL == st )
{
return 0;
}
inf = torrent_info( id );
assert( NULL != inf );
return ipc_addstat( list, id, inf, st, types );
return ipc_addstat( list, id, st, types );
}
void

244
gtk/actions.c Normal file
View File

@ -0,0 +1,244 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "transmission.h"
#include "torrent-inspector.h"
#include "img_icon_full.h"
#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif
extern void doAction (const char * action_name, gpointer user_data );
static GtkActionGroup * myGroup = 0;
static void action_cb ( GtkAction * a, gpointer user_data )
{
doAction ( gtk_action_get_name(a), user_data );
}
#if !GTK_CHECK_VERSION(2,8,0)
#define GTK_STOCK_INFO NULL
#endif
static GtkRadioActionEntry priority_toggle_entries[] =
{
{ "priority-high", NULL, N_("_High"), NULL, NULL, TR_PRI_HIGH },
{ "priority-normal", NULL, N_("_Normal"), NULL, NULL, TR_PRI_NORMAL },
{ "priority-low", NULL, N_("_Low"), NULL, NULL, TR_PRI_LOW },
{ "priority-dnd", NULL, N_("_Don't Get"), NULL, NULL, TR_PRI_DND }
};
extern void set_selected_file_priority ( tr_priority_t );
static void
priority_changed_cb (GtkAction *action UNUSED, GtkRadioAction *current)
{
const int priority = gtk_radio_action_get_current_value (current);
set_selected_file_priority ( priority );
}
static GtkActionEntry entries[] =
{
{ "file-menu", NULL, N_("_File"), NULL, NULL, NULL },
{ "edit-menu", NULL, N_("_Edit"), NULL, NULL, NULL },
{ "help-menu", NULL, N_("_Help"), NULL, NULL, NULL },
{ "priority-menu", NULL, N_("_Priority"), NULL, NULL, NULL },
{ "add-torrent", GTK_STOCK_ADD,
N_("_Add"), "<control>A", NULL, G_CALLBACK(action_cb) },
{ "start-torrent", GTK_STOCK_EXECUTE,
N_("_Start"), "<control>S", NULL, G_CALLBACK(action_cb) },
{ "recheck-torrent", GTK_STOCK_REFRESH,
N_("Re_check"), NULL, NULL, G_CALLBACK(action_cb) },
{ "stop-torrent", GTK_STOCK_STOP,
N_("S_top"), "<control>T", NULL, G_CALLBACK(action_cb) },
{ "remove-torrent", GTK_STOCK_REMOVE,
N_("_Remove"), "<control>R", NULL, G_CALLBACK(action_cb) },
{ "create-torrent", GTK_STOCK_NEW,
N_("_Create New Torrent"), NULL, NULL, G_CALLBACK(action_cb) },
{ "close", GTK_STOCK_CLOSE,
N_("_Close"), "<control>C", NULL, G_CALLBACK(action_cb) },
{ "quit", GTK_STOCK_QUIT,
N_("_Quit"), "<control>Q", NULL, G_CALLBACK(action_cb) },
{ "edit-preferences", GTK_STOCK_PREFERENCES,
N_("Edit _Preferences"), NULL, NULL, G_CALLBACK(action_cb) },
{ "show-torrent-inspector", GTK_STOCK_PROPERTIES,
N_("_Torrent Info"), NULL, NULL, G_CALLBACK(action_cb) },
{ "show-about-dialog", GTK_STOCK_ABOUT,
N_("_About Transmission"), NULL, NULL, G_CALLBACK(action_cb) },
{ "show-debug-window", GTK_STOCK_INFO,
N_("Show _Debug Window"), NULL, NULL, G_CALLBACK(action_cb) },
{ "toggle-main-window", "ICON_TRANSMISSION",
N_("Show / Hide _Transmission"), NULL, NULL, G_CALLBACK(action_cb) }
};
static void
ensure_tooltip (GtkActionEntry * e)
{
if( !e->tooltip && e->label )
{
const char * src;
char *tgt;
e->tooltip = g_malloc( strlen( e->label ) + 1 );
for( src=e->label, tgt=(char*)e->tooltip; *src; ++src )
if( *src != '_' )
*tgt++ = *src;
}
}
typedef struct
{
const guint8* raw;
const char * name;
}
BuiltinIconInfo;
/* only one icon now... but room to grow ;) */
const BuiltinIconInfo my_builtin_icons [] =
{
{ tr_icon_full, "ICON_TRANSMISSION" }
};
static void
register_my_icons ( void )
{
int i;
const int n = G_N_ELEMENTS( my_builtin_icons );
GtkIconFactory * factory = gtk_icon_factory_new ();
gtk_icon_factory_add_default( factory );
for( i=0; i<n; ++i )
{
GdkPixbuf * p;
int width;
GtkIconSet * icon_set;
p = gdk_pixbuf_new_from_inline( -1, my_builtin_icons[i].raw, FALSE, 0 );
width = gdk_pixbuf_get_width( p );
gtk_icon_theme_add_builtin_icon (my_builtin_icons[i].name, width, p );
icon_set = gtk_icon_set_new_from_pixbuf( p );
gtk_icon_factory_add( factory, my_builtin_icons[i].name, icon_set );
g_object_unref( p );
gdk_pixbuf_unref( p );
gtk_icon_set_unref (icon_set);
}
g_object_unref (G_OBJECT (factory));
}
static GtkUIManager * myUIManager = NULL;
void
actions_init( GtkUIManager * ui_manager, gpointer callback_user_data )
{
int i;
const int n_entries = G_N_ELEMENTS( entries );
GtkActionGroup * action_group;
myUIManager = ui_manager;
register_my_icons ();
for( i=0; i<n_entries; ++i )
ensure_tooltip (&entries[i]);
action_group = myGroup = gtk_action_group_new( "Actions" );
gtk_action_group_set_translation_domain( action_group, NULL );
gtk_action_group_add_radio_actions( action_group,
priority_toggle_entries,
G_N_ELEMENTS(priority_toggle_entries),
TR_PRI_NORMAL,
G_CALLBACK(priority_changed_cb), NULL);
gtk_action_group_add_actions( action_group,
entries, n_entries,
callback_user_data );
gtk_ui_manager_insert_action_group( ui_manager, action_group, 0 );
g_object_unref (G_OBJECT(action_group));
}
/****
*****
****/
static GHashTable * key_to_action = NULL;
static void
ensure_action_map_loaded (GtkUIManager * uim)
{
GList * l;
if ( key_to_action != NULL )
return;
key_to_action =
g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL);
for( l=gtk_ui_manager_get_action_groups(uim); l!=NULL; l=l->next )
{
GtkActionGroup * action_group = GTK_ACTION_GROUP( l->data );
GList *ait, *actions = gtk_action_group_list_actions( action_group );
for( ait=actions; ait!=NULL; ait=ait->next )
{
GtkAction * action = GTK_ACTION( ait->data );
const char * name = gtk_action_get_name( action );
g_hash_table_insert( key_to_action, g_strdup(name), action );
}
g_list_free( actions );
}
}
static GtkAction*
get_action( const char* name )
{
ensure_action_map_loaded( myUIManager );
return ( GtkAction* ) g_hash_table_lookup( key_to_action, name );
}
void
action_activate ( const char * name )
{
GtkAction * action = get_action( name );
g_assert( action != NULL );
gtk_action_activate( action );
}
void
action_sensitize( const char * name, gboolean b )
{
GtkAction * action = get_action( name );
g_assert( action != NULL );
g_object_set( action, "sensitive", b, NULL );
}
void
action_toggle( const char * name, gboolean b )
{
GtkAction * action = get_action( name );
gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(action), b );
}
GtkWidget*
action_get_widget( const char * path )
{
return gtk_ui_manager_get_widget( myUIManager, path );
}

26
gtk/actions.h Normal file
View File

@ -0,0 +1,26 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#ifndef TR_ACTIONS_H
#define TR_ACTIONS_H
#include <gtk/gtk.h>
void actions_init ( GtkUIManager * ui_manager, gpointer callback_user_data );
void action_activate ( const char * name );
void action_sensitize ( const char * name, gboolean b );
void action_toggle ( const char * name, gboolean b );
GtkWidget* action_get_widget ( const char * path );
#endif

View File

@ -1,20 +1,11 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pan - A Newsreader for Gtk+
* Copyright (C) 2002 Charles Kerr <charles@rebelbase.com>
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#include <gtk/gtk.h>
@ -102,7 +93,7 @@ hig_workarea_add_label_w (GtkWidget * table,
GtkWidget * l)
{
if (GTK_IS_MISC(l))
gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.0f);
gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
if (GTK_IS_LABEL(l))
gtk_label_set_use_markup (GTK_LABEL(l), TRUE);
gtk_table_attach (GTK_TABLE(table), l, 1, 2, row, row+1, GTK_FILL, GTK_FILL, 0, 0);
@ -166,3 +157,18 @@ hig_workarea_finish (GtkWidget * table,
gtk_widget_set_size_request (w, 0u, 6u);
gtk_table_attach_defaults (GTK_TABLE(table), w, 0, 4, *row, *row+1);
}
void
hig_message_dialog_set_text (GtkMessageDialog * dialog,
const char * primary,
const char * secondary)
{
#if GTK_CHECK_VERSION(2,6,0)
gtk_message_dialog_set_markup (dialog, primary);
gtk_message_dialog_format_secondary_text (dialog, "%s", secondary);
#else
char * pch = g_strdup_printf ("<b>%s</b>\n \n%s", primary, secondary);
gtk_message_dialog_set_markup (dialog, pch);
g_free (pch);
#endif
}

View File

@ -1,26 +1,12 @@
/******************************************************************************
* $Id:$
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* Copyright (c) 2007 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.
*****************************************************************************/
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#ifndef __HIG_H__
#define __HIG_H__
@ -92,6 +78,12 @@ void
hig_workarea_finish (GtkWidget * table,
int * row);
void
hig_message_dialog_set_text (GtkMessageDialog * dialog,
const char * primary,
const char * secondary);
/**
***
**/

View File

@ -788,18 +788,15 @@ static int
addinfo( TrTorrent * tor, enum ipc_msg msgid, int torid, int types,
benc_val_t * val )
{
tr_info_t * inf;
tr_stat_t * st;
inf = tr_torrent_info( tor );
if( IPC_MSG_INFO == msgid )
{
tr_info_t * inf = tr_torrent_info( tor );
return ipc_addinfo( val, torid, inf, types );
}
else
{
st = tr_torrent_stat( tor );
return ipc_addstat( val, torid, inf, st, types );
tr_stat_t * st = tr_torrent_stat( tor );
return ipc_addstat( val, torid, st, types );
}
}

View File

@ -36,9 +36,11 @@
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include "actions.h"
#include "conf.h"
#include "dialogs.h"
#include "ipc.h"
#include "makemeta-ui.h"
#include "msgwin.h"
#include "torrent-inspector.h"
#include "tr_cell_renderer_progress.h"
@ -48,12 +50,11 @@
#include "tr_torrent.h"
#include "tr_window.h"
#include "util.h"
#include "ui.h"
#include "transmission.h"
#include "version.h"
#include "img_icon_full.h"
/* time in seconds to wait for torrents to stop when exiting */
#define TRACKER_EXIT_TIMEOUT 10
@ -69,9 +70,16 @@
#if GTK_CHECK_VERSION(2,8,0)
#define SHOW_LICENSE
static const char * LICENSE =
"The Transmission binaries and source code are distributed under the MIT "
"The Transmission binaries and most of its source code is distributed "
"license. "
"\n\n"
"Some files are copyrighted by Charles Kerr and are covered by "
"the GPL version 2. Works owned by the Transmission project "
"are granted a special exemption to clause 2(b) so that the bulk "
"of its code can remain under the MIT license. This exemption does "
"not extend to original or derived works not owned by the "
"Transmission project. "
"\n\n"
"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 "
@ -95,7 +103,7 @@ static const char * LICENSE =
struct cbdata {
GtkWindow * wind;
TrCore * core;
TrIcon * icon;
GtkWidget * icon;
TrPrefs * prefs;
guint timer;
gboolean msgwinopen;
@ -109,68 +117,10 @@ struct exitdata {
guint timer;
};
enum
{
ACT_OPEN = 0,
ACT_START,
ACT_STOP,
ACT_DELETE,
ACT_SEPARATOR1,
ACT_INFO,
ACT_DEBUG,
ACT_ABOUT,
ACT_SEPARATOR2,
ACT_PREF,
ACT_SEPARATOR3,
ACT_CLOSE,
ACT_QUIT,
ACT_ICON,
ACTION_COUNT,
};
#if !GTK_CHECK_VERSION(2,8,0)
#define GTK_STOCK_INFO NULL
#endif
struct
{
const char * label;
const char * icon;
guint key;
int flags;
const char * tooltip;
}
actions[] =
{
{ NULL, GTK_STOCK_ADD, 'o', ACTF_WHEREVER | ACTF_ALWAYS,
N_("Add a new torrent") },
{ N_("Start"), GTK_STOCK_EXECUTE, 0, ACTF_WHEREVER | ACTF_INACTIVE,
N_("Start a torrent that is not running") },
{ NULL, GTK_STOCK_STOP, 0, ACTF_WHEREVER | ACTF_ACTIVE,
N_("Stop a torrent that is running") },
{ NULL, GTK_STOCK_REMOVE, 0, ACTF_WHEREVER | ACTF_WHATEVER,
N_("Remove a torrent") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_PROPERTIES, 0, ACTF_WHEREVER | ACTF_WHATEVER,
N_("Show additional information about a torrent") },
{ N_("Open debug window"), GTK_STOCK_INFO, 0, ACTF_MENU | ACTF_ALWAYS,
NULL },
{ NULL, GTK_STOCK_ABOUT, 0, ACTF_MENU | ACTF_ALWAYS,
N_("About Transmission") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_PREFERENCES, 0, ACTF_WHEREVER | ACTF_ALWAYS,
N_("Customize application behavior") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_CLOSE, 0, ACTF_MENU | ACTF_ALWAYS,
N_("Close the main window") },
{ NULL, GTK_STOCK_QUIT, 0, ACTF_MENU | ACTF_ALWAYS,
N_("Exit the program") },
/* this isn't a terminator for the list, it's ACT_ICON */
{ NULL, NULL, 0, 0, NULL },
};
#define CBDATA_PTR "callback-data-pointer"
static GtkUIManager * myUIManager = NULL;
static sig_atomic_t global_sigcount = 0;
static GList *
@ -178,17 +128,14 @@ readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
static gboolean
sendremote( GList * files, gboolean sendquit );
static void
gtksetup( int * argc, char *** argv );
gtksetup( int * argc, char *** argv, struct cbdata* );
static void
appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused );
appsetup( TrWindow * wind, benc_val_t * state, GList * args,
struct cbdata * , gboolean paused );
static void
winsetup( struct cbdata * cbdata, TrWindow * wind );
static void
iconclick( struct cbdata * cbdata );
static void
makeicon( struct cbdata * cbdata );
static gboolean
winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata );
static void
wannaquit( void * vdata );
static gboolean
@ -216,12 +163,8 @@ static gboolean
updatemodel(gpointer gdata);
static void
boolwindclosed(GtkWidget *widget, gpointer gdata);
static void
windact(GtkWidget *widget, int action, gpointer gdata);
static GList *
getselection( struct cbdata * cbdata );
static void
handleaction( struct cbdata *data, int action );
static void
safepipe(void);
@ -230,10 +173,38 @@ setupsighandlers(void);
static void
fatalsig(int sig);
static void
accumulateStatusForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer accumulated_status)
{
int status = 0;
gtk_tree_model_get( model, iter, MC_STAT, &status, -1 );
*(int*)accumulated_status |= status;
}
static void
refreshTorrentActions( GtkTreeSelection * s )
{
int status = 0;
gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &status );
action_sensitize( "stop-torrent", (status & TR_STATUS_ACTIVE) != 0);
action_sensitize( "start-torrent", (status & TR_STATUS_INACTIVE) != 0);
action_sensitize( "remove-torrent", status != 0);
action_sensitize( "show-torrent-inspector", status != 0);
}
static void
selectionChangedCB( GtkTreeSelection * s, gpointer unused UNUSED )
{
refreshTorrentActions( s );
}
int
main( int argc, char ** argv )
{
GtkWindow * mainwind;
struct cbdata * cbdata = g_new (struct cbdata, 1);
char * err;
benc_val_t * state;
GList * argfiles;
@ -249,13 +220,15 @@ main( int argc, char ** argv )
didlock = sendremote( argfiles, sendquit );
}
setupsighandlers(); /* set up handlers for fatal signals */
gtksetup( &argc, &argv ); /* set up gtk and gettext */
gtksetup( &argc, &argv, cbdata ); /* set up gtk and gettext */
if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
( didlock || cf_lock( &err ) ) )
{
GtkWindow * mainwind;
/* create main window now to be a parent to any error dialogs */
mainwind = GTK_WINDOW( tr_window_new() );
mainwind = GTK_WINDOW( tr_window_new( myUIManager ) );
/* try to load prefs and saved state */
cf_loadprefs( &err );
@ -272,7 +245,7 @@ main( int argc, char ** argv )
}
msgwin_loadpref(); /* set message level here before tr_init() */
appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused );
appsetup( mainwind, state, argfiles, cbdata, startpaused );
cf_freestate( state );
}
else
@ -378,17 +351,21 @@ sendremote( GList * files, gboolean sendquit )
}
static void
gtksetup( int * argc, char *** argv )
gtksetup( int * argc, char *** argv, struct cbdata * callback_data )
{
GdkPixbuf * icon;
gtk_init( argc, argv );
bindtextdomain( "transmission-gtk", LOCALEDIR );
bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
textdomain( "transmission-gtk" );
g_set_application_name( _("Transmission") );
gtk_init( argc, argv );
/* connect up the actions */
myUIManager = gtk_ui_manager_new ();
actions_init ( myUIManager, callback_data );
gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
gtk_ui_manager_ensure_update (myUIManager);
/* tweak some style properties in dialogs to get closer to the GNOME HiG */
gtk_rc_parse_string(
@ -400,19 +377,16 @@ gtksetup( int * argc, char *** argv )
"}\n"
"widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
gtk_window_set_default_icon( icon );
g_object_unref( icon );
gtk_window_set_default_icon_name ( "ICON_TRANSMISSION" );
}
static void
appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
appsetup( TrWindow * wind, benc_val_t * state, GList * args,
struct cbdata * cbdata, gboolean paused )
{
struct cbdata * cbdata;
enum tr_torrent_action action;
/* fill out cbdata */
cbdata = g_new0( struct cbdata, 1 );
cbdata->wind = NULL;
cbdata->core = tr_core_new();
cbdata->icon = NULL;
@ -459,96 +433,56 @@ appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
updatemodel( cbdata );
/* show the window */
tr_window_show( wind );
gtk_widget_show( GTK_WIDGET(wind) );
}
static gboolean
winclose( GtkWidget * widget UNUSED, GdkEvent * event UNUSED, gpointer gdata )
{
struct cbdata * cbdata = (struct cbdata *) gdata;
if( cbdata->icon != NULL )
gtk_widget_hide( GTK_WIDGET( cbdata->wind ) );
else
askquit( cbdata->wind, wannaquit, cbdata );
return TRUE; /* don't propagate event further */
}
static void
rowChangedCB( GtkTreeModel * model UNUSED,
GtkTreePath * path UNUSED,
GtkTreeIter * iter UNUSED,
gpointer sel)
{
refreshTorrentActions( GTK_TREE_SELECTION(sel) );
}
static void
winsetup( struct cbdata * cbdata, TrWindow * wind )
{
int ii;
GtkWidget * drag;
GtkTreeModel * model;
GtkTreeSelection * sel;
g_assert( ACTION_COUNT == ALEN( actions ) );
g_assert( NULL == cbdata->wind );
cbdata->wind = GTK_WINDOW( wind );
for( ii = 0; ii < ALEN( actions ); ii++ )
{
tr_window_action_add( wind, ii, actions[ii].flags,
gettext( actions[ii].label ), actions[ii].icon,
gettext( actions[ii].tooltip ),
actions[ii].key );
}
g_object_set( wind, "model", tr_core_model( cbdata->core ),
"double-click-action", ACT_INFO, NULL);
g_signal_connect( wind, "action", G_CALLBACK( windact ), cbdata );
sel = tr_window_get_selection( cbdata->wind );
g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
selectionChangedCB( sel, NULL );
model = tr_core_model( cbdata->core );
gtk_tree_view_set_model ( gtk_tree_selection_get_tree_view(sel), model );
g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
g_object_get( wind, "drag-widget", &drag, NULL );
setupdrag( drag, cbdata );
}
static void
iconclick( struct cbdata * cbdata )
{
GtkWidget * win;
if( NULL != cbdata->wind )
{
/* close window */
winclose( NULL, NULL, cbdata );
}
else
{
/* create window */
win = tr_window_new();
winsetup( cbdata, TR_WINDOW( win ) );
tr_window_show( TR_WINDOW( win ) );
}
setupdrag( GTK_WIDGET(wind), cbdata );
}
static void
makeicon( struct cbdata * cbdata )
{
TrIcon * icon;
int ii;
if( NULL != cbdata->icon )
{
return;
}
icon = tr_icon_new();
for( ii = 0; ii < ALEN( actions ); ii++ )
{
tr_icon_action_add( TR_ICON( icon ), ii, actions[ii].flags,
gettext( actions[ii].label ), actions[ii].icon );
}
g_object_set( icon, "activate-action", ACT_ICON, NULL);
g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata );
cbdata->icon = icon;
}
static gboolean
winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata )
{
struct cbdata * cbdata;
cbdata = gdata;
if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) )
{
gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
cbdata->wind = NULL;
}
else
{
askquit( cbdata->wind, wannaquit, cbdata );
}
/* don't propagate event further */
return TRUE;
if( NULL == cbdata->icon )
cbdata->icon = tr_icon_new( );
}
static void
@ -862,6 +796,7 @@ prefschanged( TrCore * core SHUTUP, int id, gpointer data )
}
else if( NULL != cbdata->icon )
{
g_message ("foo");
g_object_unref( cbdata->icon );
cbdata->icon = NULL;
}
@ -909,7 +844,7 @@ updatemodel(gpointer gdata) {
if( NULL != data->wind )
{
tr_torrentRates( tr_core_handle( data->core ), &down, &up );
tr_window_update( TR_WINDOW( data->wind ), down, up );
tr_window_update( data->wind, down, up );
}
/* update the message window */
@ -925,32 +860,25 @@ boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
*preachy_gcc = FALSE;
}
static void
windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
{
g_assert( 0 <= action );
handleaction( gdata, action );
}
/* returns a GList containing a GtkTreeRowReference to each selected row */
static GList *
getselection( struct cbdata * cbdata )
{
GtkTreeSelection * sel;
GList * rows, * ii;
GtkTreeRowReference * ref;
GList * rows = NULL;
if( NULL == cbdata->wind )
if( NULL != cbdata->wind )
{
return NULL;
}
g_object_get( cbdata->wind, "selection", &sel, NULL );
rows = gtk_tree_selection_get_selected_rows( sel, NULL );
for( ii = rows; NULL != ii; ii = ii->next )
{
ref = gtk_tree_row_reference_new( tr_core_model( cbdata->core ), ii->data );
gtk_tree_path_free( ii->data );
ii->data = ref;
GList * ii;
GtkTreeSelection *s = tr_window_get_selection(cbdata->wind);
GtkTreeModel * model = tr_core_model( cbdata->core );
rows = gtk_tree_selection_get_selected_rows( s, NULL );
for( ii = rows; NULL != ii; ii = ii->next )
{
GtkTreeRowReference * ref = gtk_tree_row_reference_new(
model, ii->data );
gtk_tree_path_free( ii->data );
ii->data = ref;
}
}
return rows;
@ -983,129 +911,168 @@ about ( void )
}
static void
handleaction( struct cbdata * data, int act )
startTorrentForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer data UNUSED)
{
GList *rows, *ii;
GtkTreeModel * model;
GtkTreePath *path;
GtkTreeIter iter;
TrTorrent *tor;
int status;
gboolean changed;
GtkWidget * win;
g_assert( ACTION_COUNT > act );
switch( act )
{
case ACT_ABOUT:
about ();
return;
case ACT_OPEN:
makeaddwind( data->wind, data->core );
return;
case ACT_PREF:
if( NULL != data->prefs )
{
return;
}
data->prefs = tr_prefs_new_with_parent( G_OBJECT( data->core ),
data->wind );
g_signal_connect( data->prefs, "destroy",
G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
gtk_widget_show( GTK_WIDGET( data->prefs ) );
return;
case ACT_DEBUG:
if( !data->msgwinopen )
{
data->msgwinopen = TRUE;
win = msgwin_create();
g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
&data->msgwinopen );
}
return;
case ACT_ICON:
iconclick( data );
return;
case ACT_CLOSE:
if( NULL != data->wind )
{
winclose( NULL, NULL, data );
}
return;
case ACT_QUIT:
askquit( data->wind, wannaquit, data );
return;
case ACT_SEPARATOR1:
case ACT_SEPARATOR2:
case ACT_SEPARATOR3:
return;
case ACT_START:
case ACT_STOP:
case ACT_DELETE:
case ACT_INFO:
case ACTION_COUNT:
break;
}
/* get a list of references to selected rows */
rows = getselection( data );
model = tr_core_model( data->core );
changed = FALSE;
for(ii = rows; NULL != ii; ii = ii->next) {
if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
gtk_tree_model_get_iter( model, &iter, path ) )
{
gtk_tree_model_get( model , &iter, MC_TORRENT, &tor,
MC_STAT, &status, -1 );
if( ACT_ISAVAIL( actions[act].flags, status ) )
{
switch( act )
{
case ACT_START:
tr_torrent_start( tor );
changed = TRUE;
break;
case ACT_STOP:
tr_torrent_stop( tor );
changed = TRUE;
break;
case ACT_DELETE:
tr_core_delete_torrent( data->core, &iter );
changed = TRUE;
break;
case ACT_INFO:
gtk_widget_show (torrent_inspector_new (data->wind, tor));
break;
case ACT_OPEN:
case ACT_PREF:
case ACT_DEBUG:
case ACT_ICON:
case ACT_CLOSE:
case ACT_QUIT:
case ACT_SEPARATOR1:
case ACT_SEPARATOR2:
case ACT_SEPARATOR3:
case ACTION_COUNT:
break;
}
}
g_object_unref(tor);
}
if(NULL != path)
gtk_tree_path_free(path);
gtk_tree_row_reference_free(ii->data);
}
g_list_free(rows);
if(changed) {
updatemodel(data);
tr_core_save( data->core );
}
TrTorrent * tor = NULL;
gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
tr_torrent_start( tor );
g_object_unref( G_OBJECT( tor ) );
}
static void
stopTorrentForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer data UNUSED)
{
TrTorrent * tor = NULL;
gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
tr_torrent_stop( tor );
g_object_unref( G_OBJECT( tor ) );
}
static void
showInfoForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer data UNUSED)
{
TrTorrent * tor = NULL;
GtkWidget * w;
gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
w = torrent_inspector_new( GTK_WINDOW(data), tor );
gtk_widget_show( w );
g_object_unref( G_OBJECT( tor ) );
}
static void
recheckTorrentForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer data UNUSED)
{
TrTorrent * gtor = NULL;
int status = 0;
tr_torrent_t * tor;
gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, MC_STAT, &status, -1 );
tor = tr_torrent_handle( gtor );
if( status & TR_STATUS_ACTIVE )
tr_torrentStop( tor );
tr_torrentRemoveFastResume( tor );
tr_torrentStart( tor );
g_object_unref( G_OBJECT( gtor ) );
}
void
doAction ( const char * action_name, gpointer user_data )
{
struct cbdata * data = (struct cbdata *) user_data;
gboolean changed = FALSE;
if (!strcmp (action_name, "add-torrent"))
{
makeaddwind( data->wind, data->core );
}
else if (!strcmp (action_name, "start-torrent"))
{
GtkTreeSelection * s = tr_window_get_selection(data->wind);
gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
}
else if (!strcmp (action_name, "stop-torrent"))
{
GtkTreeSelection * s = tr_window_get_selection(data->wind);
gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
}
else if (!strcmp (action_name, "recheck-torrent"))
{
GtkTreeSelection * s = tr_window_get_selection(data->wind);
gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
}
else if (!strcmp (action_name, "show-torrent-inspector"))
{
GtkTreeSelection * s = tr_window_get_selection(data->wind);
gtk_tree_selection_selected_foreach( s, showInfoForeach, data->wind );
}
else if (!strcmp (action_name, "create-torrent"))
{
GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), tr_core_handle( data->core ) );
gtk_widget_show_all( w );
}
else if (!strcmp (action_name, "remove-torrent"))
{
/* this modifies the model's contents, so can't use foreach */
GList *l, *sel = getselection( data );
GtkTreeModel *model = tr_core_model( data->core );
for( l=sel; l!=NULL; l=l->next )
{
GtkTreeIter iter;
GtkTreeRowReference * reference = (GtkTreeRowReference *) l->data;
GtkTreePath * path = gtk_tree_row_reference_get_path( reference );
gtk_tree_model_get_iter( model, &iter, path );
tr_core_delete_torrent( data->core, &iter );
gtk_tree_row_reference_free( reference );
changed = TRUE;
}
g_list_free( sel );
}
else if (!strcmp (action_name, "close"))
{
if( data->wind != NULL )
winclose( NULL, NULL, data );
}
else if (!strcmp (action_name, "quit"))
{
askquit( data->wind, wannaquit, data );
}
else if (!strcmp (action_name, "edit-preferences"))
{
if( NULL == data->prefs )
{
data->prefs = tr_prefs_new_with_parent( G_OBJECT( data->core ),
data->wind );
g_signal_connect( data->prefs, "destroy",
G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
gtk_widget_show( GTK_WIDGET( data->prefs ) );
}
}
else if (!strcmp (action_name, "show-debug-window"))
{
if( !data->msgwinopen )
{
GtkWidget * win = msgwin_create();
g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
&data->msgwinopen );
data->msgwinopen = TRUE;
}
}
else if (!strcmp (action_name, "show-about-dialog"))
{
about();
}
else if (!strcmp (action_name, "toggle-main-window"))
{
GtkWidget * w = GTK_WIDGET (data->wind);
if (GTK_WIDGET_VISIBLE(w))
gtk_widget_hide (w);
else
gtk_window_present (GTK_WINDOW(w));
}
else g_error ("Unhandled action: %s", action_name );
if(changed)
{
updatemodel( data );
tr_core_save( data->core );
}
}
static void
safepipe(void) {
struct sigaction sa;

300
gtk/makemeta-ui.c Normal file
View File

@ -0,0 +1,300 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "transmission.h"
#include "makemeta.h"
#include "hig.h"
#include "makemeta-ui.h"
#include "util.h"
#define UPDATE_INTERVAL_MSEC 200
typedef struct
{
char torrent_name[2048];
GtkWidget * size_lb;
GtkWidget * pieces_lb;
GtkWidget * announce_entry;
GtkWidget * comment_entry;
GtkWidget * progressbar;
GtkWidget * private_check;
GtkWidget * dialog;
GtkWidget * progress_dialog;
tr_metainfo_builder_t * builder;
tr_handle_t * handle;
}
MakeMetaUI;
static void
freeMetaUI( gpointer p )
{
MakeMetaUI * ui = (MakeMetaUI *) p;
tr_metaInfoBuilderFree( ui->builder );
memset( ui, ~0, sizeof(MakeMetaUI) );
g_free( ui );
}
static void
progress_response_cb ( GtkDialog *d UNUSED, int response, gpointer user_data )
{
MakeMetaUI * ui = (MakeMetaUI *) user_data;
if( response == GTK_RESPONSE_CANCEL )
{
ui->builder->abortFlag = TRUE;
}
else
{
gtk_widget_destroy( ui->dialog );
}
}
static gboolean
refresh_cb ( gpointer user_data )
{
char buf[1024];
double fraction;
MakeMetaUI * ui = (MakeMetaUI *) user_data;
GtkProgressBar * p = GTK_PROGRESS_BAR( ui->progressbar );
fraction = (double)ui->builder->pieceIndex / ui->builder->pieceCount;
gtk_progress_bar_set_fraction( p, fraction );
g_snprintf( buf, sizeof(buf), "%s (%d%%)", ui->torrent_name, (int)(fraction*100 + 0.5));
gtk_progress_bar_set_text( p, buf );
if( ui->builder->isDone )
{
GtkWidget * w;
if( ui->builder->failed )
{
const char * reason = ui->builder->abortFlag
? _("Torrent creation aborted.")
: _("Torrent creation failed.");
w = gtk_message_dialog_new (GTK_WINDOW(ui->progress_dialog),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE, reason );
gtk_dialog_run( GTK_DIALOG( w ) );
gtk_widget_destroy( ui->progress_dialog );
}
else
{
GtkWidget * w = ui->progress_dialog;
gtk_window_set_title (GTK_WINDOW(ui->progress_dialog), _("Torrent Created"));
gtk_dialog_set_response_sensitive (GTK_DIALOG(w), GTK_RESPONSE_CANCEL, FALSE);
gtk_dialog_set_response_sensitive (GTK_DIALOG(w), GTK_RESPONSE_CLOSE, TRUE);
gtk_progress_bar_set_text( p, buf );
}
}
return !ui->builder->isDone;
}
static void
remove_tag (gpointer tag)
{
g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
}
static void
response_cb( GtkDialog* d, int response, gpointer user_data )
{
MakeMetaUI * ui = (MakeMetaUI*) user_data;
GtkWidget *w, *p, *fr;
char *tmp;
char buf[1024];
guint tag;
if( response != GTK_RESPONSE_ACCEPT )
{
gtk_widget_destroy( GTK_WIDGET( d ) );
return;
}
w = gtk_dialog_new_with_buttons( _("Making Torrent..."),
GTK_WINDOW(d),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
NULL );
g_signal_connect( w, "response", G_CALLBACK(progress_response_cb), ui );
ui->progress_dialog = w;
gtk_dialog_set_response_sensitive (GTK_DIALOG(w), GTK_RESPONSE_CLOSE, FALSE);
tmp = g_path_get_basename (ui->builder->top);
g_snprintf( ui->torrent_name, sizeof(ui->torrent_name), "%s.torrent", tmp );
g_snprintf( buf, sizeof(buf), "%s (%d%%)", ui->torrent_name, 0);
p = ui->progressbar = gtk_progress_bar_new ();
gtk_progress_bar_set_text( GTK_PROGRESS_BAR(p), buf );
fr = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_NONE);
gtk_container_set_border_width( GTK_CONTAINER(fr), 20 );
gtk_container_add (GTK_CONTAINER(fr), p);
gtk_box_pack_start_defaults( GTK_BOX(GTK_DIALOG(w)->vbox), fr );
gtk_widget_show_all ( w );
g_free( tmp );
tr_makeMetaInfo( ui->builder,
NULL,
gtk_entry_get_text( GTK_ENTRY( ui->announce_entry ) ),
gtk_entry_get_text( GTK_ENTRY( ui->comment_entry ) ),
gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( ui->private_check ) ) );
tag = g_timeout_add (UPDATE_INTERVAL_MSEC, refresh_cb, ui);
g_object_set_data_full (G_OBJECT(w), "tag", GUINT_TO_POINTER(tag), remove_tag);
}
static void
file_mode_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data)
{
if( gtk_toggle_button_get_active( togglebutton ) )
{
GtkFileChooserButton * w = GTK_FILE_CHOOSER_BUTTON(user_data);
gtk_file_chooser_set_action( GTK_FILE_CHOOSER( w ), GTK_FILE_CHOOSER_ACTION_OPEN );
}
}
static void
dir_mode_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data)
{
if( gtk_toggle_button_get_active( togglebutton ) )
{
GtkFileChooserButton * w = GTK_FILE_CHOOSER_BUTTON(user_data);
gtk_file_chooser_set_action( GTK_FILE_CHOOSER( w ), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
}
}
/***
****
***/
static void
file_selection_changed_cb( GtkFileChooser *chooser, gpointer user_data )
{
MakeMetaUI * ui = (MakeMetaUI *) user_data;
char * pch;
char * filename;
char buf[512];
size_t fileCount=0, totalSize=0;
size_t pieceCount=0, pieceSize=0;
if( ui->builder ) {
tr_metaInfoBuilderFree( ui->builder );
ui->builder = NULL;
}
filename = gtk_file_chooser_get_filename( chooser );
if( filename ) {
ui->builder = tr_metaInfoBuilderCreate( ui->handle, filename );
g_free( filename );
fileCount = ui->builder->fileCount;
totalSize = ui->builder->totalSize;
pieceCount = ui->builder->pieceCount;
pieceSize = ui->builder->pieceSize;
}
pch = readablesize( totalSize );
g_snprintf( buf, sizeof(buf), "<i>%s; %lu %s</i>",
pch, fileCount,
ngettext("file", "files", fileCount) );
gtk_label_set_markup ( GTK_LABEL(ui->size_lb), buf );
g_free( pch );
pch = readablesize( pieceSize );
g_snprintf( buf, sizeof(buf), "<i>%lu %s @ %s</i>",
pieceCount,
ngettext("piece", "pieces", pieceCount),
pch );
gtk_label_set_markup ( GTK_LABEL(ui->pieces_lb), buf );
g_free( pch );
}
GtkWidget*
make_meta_ui( GtkWindow * parent, tr_handle_t * handle )
{
int row = 0;
GtkWidget *d, *t, *w, *h, *rb_file, *rb_dir;
char name[256];
MakeMetaUI * ui = g_new0 ( MakeMetaUI, 1 );
ui->handle = handle;
d = gtk_dialog_new_with_buttons( _("Make a New Torrent"),
parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_NEW, GTK_RESPONSE_ACCEPT,
NULL );
g_signal_connect( d, "response", G_CALLBACK(response_cb), ui );
g_object_set_data_full( G_OBJECT(d), "ui", ui, freeMetaUI );
ui->dialog = d;
t = hig_workarea_create ();
hig_workarea_add_section_title (t, &row, _("Files"));
hig_workarea_add_section_spacer (t, row, 3);
g_snprintf( name, sizeof(name), "%s:", _("File _Type"));
h = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
w = rb_dir = gtk_radio_button_new_with_mnemonic( NULL, _("Directory"));
gtk_box_pack_start ( GTK_BOX(h), w, FALSE, FALSE, 0 );
w = rb_file = gtk_radio_button_new_with_mnemonic_from_widget( GTK_RADIO_BUTTON(w), _("Single File") );
gtk_box_pack_start ( GTK_BOX(h), w, FALSE, FALSE, 0 );
hig_workarea_add_row (t, &row, name, h, NULL);
g_snprintf( name, sizeof(name), "%s:", _("_File"));
w = gtk_file_chooser_button_new( _("File or Directory to Add to the New Torrent"),
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
g_signal_connect( w, "selection-changed", G_CALLBACK(file_selection_changed_cb), ui );
g_signal_connect( rb_file, "toggled", G_CALLBACK(file_mode_toggled_cb), w );
g_signal_connect( rb_dir, "toggled", G_CALLBACK(dir_mode_toggled_cb), w );
hig_workarea_add_row (t, &row, name, w, NULL);
g_snprintf( name, sizeof(name), "<i>%s</i>", _("No Files Selected"));
h = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
w = ui->size_lb = gtk_label_new (NULL);
gtk_label_set_markup ( GTK_LABEL(w), name );
gtk_box_pack_start( GTK_BOX(h), w, FALSE, FALSE, 0 );
w = ui->pieces_lb = gtk_label_new (NULL);
gtk_box_pack_end( GTK_BOX(h), w, FALSE, FALSE, 0 );
w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
gtk_widget_set_usize (w, 2 * GUI_PAD_BIG, 0);
gtk_box_pack_start_defaults ( GTK_BOX(h), w );
hig_workarea_add_row (t, &row, "", h, NULL);
hig_workarea_add_section_divider( t, &row );
hig_workarea_add_section_title (t, &row, _("Torrent"));
hig_workarea_add_section_spacer (t, row, 3);
g_snprintf( name, sizeof(name), _("Private to this Tracker") );
w = ui->private_check = hig_workarea_add_wide_checkbutton( t, &row, name, FALSE );
g_snprintf( name, sizeof(name), "%s:", _("Announce _URL"));
w = ui->announce_entry = gtk_entry_new( );
gtk_entry_set_text(GTK_ENTRY(w), "http://");
hig_workarea_add_row (t, &row, name, w, NULL );
g_snprintf( name, sizeof(name), "%s:", _("Commen_t"));
w = ui->comment_entry = gtk_entry_new( );
hig_workarea_add_row (t, &row, name, w, NULL );
gtk_window_set_default_size( GTK_WINDOW(d), 400u, 0u );
gtk_box_pack_start_defaults( GTK_BOX(GTK_DIALOG(d)->vbox), t );
gtk_widget_show_all( GTK_DIALOG(d)->vbox );
return d;
}

19
gtk/makemeta-ui.h Normal file
View File

@ -0,0 +1,19 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#ifndef MAKE_META_UI__H
#define MAKE_META_UI__H
#include <gtk/gtk.h>
#include "transmission.h"
GtkWidget* make_meta_ui( GtkWindow * parent, tr_handle_t * handle );
#endif

5
gtk/my-valgrind.sh Executable file
View File

@ -0,0 +1,5 @@
#/bin/sh
export G_SLICE=always-malloc
export G_DEBUG=gc-friendly
export GLIBCXX_FORCE_NEW=1
valgrind --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=64 --log-file=x-valgrind --show-reachable=yes ./transmission-gtk

View File

@ -26,8 +26,11 @@
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "transmission.h"
#include "platform.h" /* for tr_getTorrentsDirectory */
#include "actions.h"
#include "tr_torrent.h"
#include "dot-icons.h"
#include "hig.h"
@ -215,7 +218,7 @@ refresh_pieces (GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor)
gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
grid_bounds.x, grid_bounds.y,
grid_bounds.width, grid_bounds.height-1);
grid_bounds.width-1, grid_bounds.height-1);
g_free (pieces);
g_free (completeness);
@ -596,7 +599,6 @@ static GtkWidget* peer_page_new ( TrTorrent * gtor )
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER(w), v);
gtk_widget_set_usize (w, 500u, 0u);
vbox = gtk_vbox_new (FALSE, GUI_PAD);
@ -802,7 +804,6 @@ refresh_activity (GtkWidget * top)
{
Activity * a = (Activity*) g_object_get_data (G_OBJECT(top), "activity-data");
const tr_stat_t * stat = tr_torrent_stat( a->gtor );
const tr_info_t * info = tr_torrent_info( a->gtor );
guint64 size;
char *pch;
@ -810,7 +811,7 @@ refresh_activity (GtkWidget * top)
gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
g_free (pch);
size = info->totalSize - stat->left;
size = stat->downloadedValid;
pch = readablesize (size);
gtk_label_set_text (GTK_LABEL(a->valid_dl_lb), pch);
g_free (pch);
@ -940,6 +941,7 @@ enum
FC_KEY,
FC_INDEX,
FC_SIZE,
FC_PRIORITY,
N_FILE_COLS
};
@ -948,20 +950,44 @@ typedef struct
TrTorrent * gtor;
GtkTreeModel * model; /* same object as store, but recast */
GtkTreeStore * store; /* same object as model, but recast */
GtkTreeSelection * selection;
}
FileData;
static const char*
priorityToString( const int priority )
{
switch( priority ) {
case TR_PRI_HIGH: return _("High");
case TR_PRI_NORMAL: return _("Normal");
case TR_PRI_LOW: return _("Low");
case TR_PRI_DND: return _("Don't Get");
default: return "BUG!";
}
}
static tr_priority_t
stringToPriority( const char* str )
{
if( !strcmp( str, _( "High" ) ) ) return TR_PRI_HIGH;
if( !strcmp( str, _( "Low" ) ) ) return TR_PRI_LOW;
if( !strcmp( str, _( "Don't Get" ) ) ) return TR_PRI_DND;;
return TR_PRI_NORMAL;
}
static void
parsepath( GtkTreeStore * store,
GtkTreeIter * ret,
const char * path,
int index,
uint64_t size )
parsepath( const tr_torrent_t * tor,
GtkTreeStore * store,
GtkTreeIter * ret,
const char * path,
int index,
uint64_t size )
{
GtkTreeModel * model;
GtkTreeIter * parent, start, iter;
char * file, * lower, * mykey, *escaped=0;
const char * stock;
int priority = 0;
model = GTK_TREE_MODEL( store );
parent = NULL;
@ -969,7 +995,7 @@ parsepath( GtkTreeStore * store,
if( 0 != strcmp( file, path ) )
{
char * dir = g_path_get_dirname( path );
parsepath( store, &start, dir, index, size );
parsepath( tor, store, &start, dir, index, size );
parent = &start;
g_free( dir );
}
@ -999,9 +1025,14 @@ parsepath( GtkTreeStore * store,
index = -1;
}
if (index != -1)
priority = tr_torrentGetFilePriority( tor, index );
escaped = g_markup_escape_text (file, -1);
gtk_tree_store_set( store, &iter, FC_INDEX, index, FC_LABEL, escaped,
FC_KEY, mykey, FC_STOCK, stock, FC_SIZE, size, -1 );
FC_KEY, mykey, FC_STOCK, stock,
FC_PRIORITY, priorityToString(priority),
FC_SIZE, size, -1 );
done:
g_free( escaped );
g_free( mykey );
@ -1060,6 +1091,7 @@ updateprogress( GtkTreeModel * model,
if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
{
int oldProg, newProg;
guint64 subGot, subTotal;
if (gtk_tree_model_iter_has_child( model, &iter ) )
@ -1079,8 +1111,14 @@ updateprogress( GtkTreeModel * model,
if (!subTotal) subTotal = 1; /* avoid div by zero */
g_assert (subGot <= subTotal);
gtk_tree_store_set (store, &iter,
FC_PROG, (int)(100.0*subGot/subTotal + 0.5), -1);
/* why not just set it every time?
because that causes the "priorities" combobox to pop down */
gtk_tree_model_get (model, &iter, FC_PROG, &oldProg, -1);
newProg = (int)(100.0*subGot/subTotal + 0.5);
if (oldProg != newProg)
gtk_tree_store_set (store, &iter,
FC_PROG, (int)(100.0*subGot/subTotal + 0.5), -1);
gotSize += subGot;
totalSize += subTotal;
@ -1091,18 +1129,142 @@ updateprogress( GtkTreeModel * model,
*setmeTotalSize = totalSize;
}
static GtkTreeModel*
priority_model_new (void)
{
GtkTreeIter iter;
GtkListStore * store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, _("High"), 1, TR_PRI_HIGH, -1);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, _("Normal"), 1, TR_PRI_NORMAL, -1);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, _("Low"), 1, TR_PRI_LOW, -1);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, _("Don't Get"), 1, TR_PRI_DND, -1);
return GTK_TREE_MODEL (store);
}
static void
refreshPriorityActions( GtkTreeSelection * sel )
{
GtkTreeIter iter;
GtkTreeModel * model;
const gboolean has_selection = gtk_tree_selection_get_selected( sel, &model, &iter );
action_sensitize ( "priority-high", has_selection );
action_sensitize ( "priority-normal", has_selection );
action_sensitize ( "priority-low", has_selection );
action_sensitize ( "priority-dnd", has_selection );
if( has_selection )
{
/* set the action priority base on the model's values */
char * pch = NULL;
const char * key;
gtk_tree_model_get( model, &iter, FC_PRIORITY, &pch, -1 );
switch( stringToPriority( pch ) ) {
case TR_PRI_HIGH: key = "priority-high"; break;
case TR_PRI_LOW: key = "priority-low"; break;
case TR_PRI_DND: key = "priority-dnd"; break;
default: key = "priority-normal"; break;
}
action_toggle( key, TRUE );
g_free( pch );
}
}
static void
set_priority (GtkTreeSelection * selection,
GtkTreeStore * store,
GtkTreeIter * iter,
tr_torrent_t * tor,
int priority_val,
const char * priority_str)
{
int index;
GtkTreeIter child;
gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1 );
if (index >= 0)
tr_torrentSetFilePriority( tor, index, priority_val );
gtk_tree_store_set( store, iter, FC_PRIORITY, priority_str, -1 );
if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
set_priority( selection, store, &child, tor, priority_val, priority_str );
while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
refreshPriorityActions( selection );
}
static void
priority_changed_cb (GtkCellRendererText * cell UNUSED,
const gchar * path,
const gchar * value,
void * file_data)
{
GtkTreeIter iter;
FileData * d = (FileData*) file_data;
if (gtk_tree_model_get_iter_from_string (d->model, &iter, path))
{
tr_torrent_t * tor = tr_torrent_handle( d->gtor );
const tr_priority_t priority = stringToPriority( value );
set_priority( d->selection, d->store, &iter, tor, priority, value );
}
}
/* FIXME: NULL this back out when popup goes down */
static GtkWidget * popupView = NULL;
static void
on_popup_menu ( GtkWidget * view, GdkEventButton * event )
{
GtkWidget * menu = action_get_widget ( "/file-popup" );
popupView = view;
gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL,
(event ? event->button : 0),
(event ? event->time : 0));
}
static void
fileSelectionChangedCB( GtkTreeSelection * sel, gpointer unused UNUSED )
{
refreshPriorityActions( sel );
}
void
set_selected_file_priority ( tr_priority_t priority_val )
{
if( popupView && GTK_IS_TREE_VIEW(popupView) )
{
GtkTreeView * view = GTK_TREE_VIEW( popupView );
tr_torrent_t * tor = (tr_torrent_t*)
g_object_get_data (G_OBJECT(view), "torrent-handle");
const char * priority_str = priorityToString( priority_val );
GtkTreeModel * model;
GtkTreeIter iter;
GtkTreeSelection * sel = gtk_tree_view_get_selection (view);
gtk_tree_selection_get_selected( sel, &model, &iter );
set_priority( sel, GTK_TREE_STORE(model), &iter,
tor, priority_val, priority_str );
}
}
GtkWidget *
file_page_new ( TrTorrent * gtor )
{
GtkWidget * ret;
FileData * data;
tr_info_t * inf;
GtkTreeStore * store;
int ii;
GtkWidget * view, * scroll;
GtkCellRenderer * rend;
GtkTreeViewColumn * col;
GtkTreeSelection * sel;
GtkWidget * ret;
FileData * data;
tr_info_t * inf;
tr_torrent_t * tor;
GtkTreeStore * store;
int ii;
GtkWidget * view, * scroll;
GtkCellRenderer * rend;
GtkTreeViewColumn * col;
GtkTreeSelection * sel;
GtkTreeModel * model;
store = gtk_tree_store_new ( N_FILE_COLS,
G_TYPE_STRING, /* stock */
@ -1110,13 +1272,15 @@ file_page_new ( TrTorrent * gtor )
G_TYPE_INT, /* prog [0..100] */
G_TYPE_STRING, /* key */
G_TYPE_INT, /* index */
G_TYPE_UINT64); /* size */
G_TYPE_UINT64, /* size */
G_TYPE_STRING); /* priority */
/* set up the model */
tor = tr_torrent_handle( gtor );
inf = tr_torrent_info( gtor );
for( ii = 0; ii < inf->fileCount; ii++ )
{
parsepath( store, NULL, STRIPROOT( inf->files[ii].name ),
parsepath( tor, store, NULL, STRIPROOT( inf->files[ii].name ),
ii, inf->files[ii].length );
}
getdirtotals( store, NULL );
@ -1125,6 +1289,12 @@ file_page_new ( TrTorrent * gtor )
/* create the view */
view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
g_object_set_data (G_OBJECT(view), "torrent-handle", tor );
g_signal_connect( view, "popup-menu",
G_CALLBACK(on_popup_menu), NULL );
g_signal_connect( view, "button-press-event",
G_CALLBACK(on_tree_view_button_pressed), on_popup_menu);
/* add file column */
col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
@ -1150,6 +1320,24 @@ file_page_new ( TrTorrent * gtor )
sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
g_signal_connect( sel, "changed", G_CALLBACK(fileSelectionChangedCB), NULL );
fileSelectionChangedCB( sel, NULL );
/* add priority column */
model = priority_model_new ();
col = gtk_tree_view_column_new ();
gtk_tree_view_column_set_title (col, _("Priority"));
rend = gtk_cell_renderer_combo_new ();
gtk_tree_view_column_pack_start (col, rend, TRUE);
g_object_set (G_OBJECT(rend), "model", model,
"editable", TRUE,
"has-entry", FALSE,
"text-column", 0,
NULL);
g_object_unref (G_OBJECT(model));
gtk_tree_view_column_add_attribute (col, rend, "text", FC_PRIORITY);
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
/* create the scrolled window and stick the view in it */
scroll = gtk_scrolled_window_new( NULL, NULL );
@ -1166,7 +1354,9 @@ file_page_new ( TrTorrent * gtor )
data->gtor = gtor;
data->model = GTK_TREE_MODEL(store);
data->store = store;
data->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
g_object_set_data_full (G_OBJECT(ret), "file-data", data, g_free);
g_signal_connect (G_OBJECT(rend), "edited", G_CALLBACK(priority_changed_cb), data);
return ret;
}
@ -1271,7 +1461,7 @@ options_page_new ( TrTorrent * gtor )
hig_workarea_add_section_title (t, &row, _("Seeding"));
hig_workarea_add_section_spacer (t, row, 1);
tb = gtk_check_button_new_with_mnemonic (_("Stop Seeding at Ratio:"));
tb = gtk_check_button_new_with_mnemonic (_("_Stop Seeding at Ratio:"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), gtor->seeding_cap_enabled);
g_signal_connect (tb, "toggled", G_CALLBACK(seeding_cap_toggled_cb), gtor);
a = (GtkAdjustment*) gtk_adjustment_new (gtor->seeding_cap, 0.0, G_MAXDOUBLE, 1, 1, 1);

View File

@ -28,6 +28,10 @@
#include <gtk/gtkwindow.h>
#include "tr_torrent.h"
struct TrTorrent;
GtkWidget* torrent_inspector_new ( GtkWindow * parent, TrTorrent * tor );
void set_selected_file_priority ( tr_priority_t );
#endif /* TG_PREFS_H */

View File

@ -218,8 +218,8 @@ tr_core_init( GTypeInstance * instance, gpointer g_class SHUTUP )
{
/* info->name, info->totalSize, info->hashString, status, */
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_INT,
/* error, errorString, progress, rateDownload, rateUpload, */
G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT,
/* error, errorString, percentComplete, percentDone, rateDownload, rateUpload, */
G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT,
/* eta, peersTotal, peersUploading, peersDownloading, seeders, */
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
/* leechers, completedFromTracker, downloaded, uploaded */
@ -264,16 +264,13 @@ tr_core_dispose( GObject * obj )
#endif
/* sever all remaining torrents in the model */
if( gtk_tree_model_get_iter_first( self->model, &iter ) )
if( gtk_tree_model_get_iter_first( self->model, &iter ) ) do
{
do
{
gtk_tree_model_get( self->model, &iter, MC_TORRENT, &tor, -1 );
tr_torrent_sever( tor );
g_object_unref( tor );
}
while( gtk_tree_model_iter_next( self->model, &iter ) );
gtk_tree_model_get( self->model, &iter, MC_TORRENT, &tor, -1 );
tr_torrent_sever( tor );
g_object_unref( tor );
}
while( gtk_tree_model_iter_next( self->model, &iter ) );
g_object_unref( self->model );
/* sever and unref all remaining zombie torrents */
@ -308,27 +305,17 @@ tr_core_new( void )
GtkTreeModel *
tr_core_model( TrCore * self )
{
TR_IS_CORE( self );
g_return_val_if_fail (TR_IS_CORE(self), NULL);
if( self->disposed )
{
return NULL;
}
return self->model;
return self->disposed ? NULL : self->model;
}
tr_handle_t *
tr_core_handle( TrCore * self )
{
TR_IS_CORE( self );
g_return_val_if_fail (TR_IS_CORE(self), NULL);
if( self->disposed )
{
return NULL;
}
return self->handle;
return self->disposed ? NULL : self->handle;
}
void
@ -746,7 +733,8 @@ tr_core_update( TrCore * self )
MC_STAT, st->status,
MC_ERR, st->error,
MC_TERR, st->errorString,
MC_PROG, st->progress,
MC_PROG_C, st->percentComplete,
MC_PROG_D, st->percentDone,
MC_DRATE, st->rateDownload,
MC_URATE, st->rateUpload,
MC_ETA, st->eta,

View File

@ -188,7 +188,7 @@ tr_core_set_pref_int( TrCore * self, int id, int val );
/* keep this in sync with the type array in tr_core_init() in tr_core.c */
enum {
MC_NAME, MC_SIZE, MC_HASH, MC_STAT, MC_ERR, MC_TERR,
MC_PROG, MC_DRATE, MC_URATE, MC_ETA, MC_PEERS,
MC_PROG_C, MC_PROG_D, MC_DRATE, MC_URATE, MC_ETA, MC_PEERS,
MC_UPEERS, MC_DPEERS, MC_SEED, MC_LEECH, MC_DONE,
MC_DOWN, MC_UP, MC_LEFT, MC_TRACKER, MC_TORRENT, MC_ID,
MC_ROW_COUNT

View File

@ -23,340 +23,46 @@
*****************************************************************************/
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "actions.h"
#include "tr_icon.h"
#include "util.h"
#define ITEM_ACTION "tr-icon-item-action"
#ifndef STATUS_ICON_SUPPORTED
enum
{
PROP_ICON = 1,
PROP_DOCKED,
PROP_CLICK,
};
static void
tr_icon_init( GTypeInstance * instance, gpointer g_class );
static void
tr_icon_set_property( GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec );
static void
tr_icon_get_property( GObject * object, guint property_id,
GValue * value, GParamSpec * pspec);
static void
tr_icon_class_init( gpointer g_class, gpointer g_class_data );
static void
tr_icon_dispose( GObject * obj );
static void
itemclick( GObject * obj, gpointer data );
static void
emitaction( TrIcon * self, int id );
#ifdef TR_ICON_SUPPORTED
static void
clicked( TrIcon * self, gpointer data );
static void
popup( TrIcon * self, guint button, guint when, gpointer data );
#endif
GType
tr_icon_get_type( void )
{
static GType type = 0;
if( 0 == type )
{
static const GTypeInfo info =
{
sizeof( TrIconClass ),
NULL, /* base_init */
NULL, /* base_finalize */
tr_icon_class_init, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
sizeof( TrIcon ),
0, /* n_preallocs */
tr_icon_init, /* instance_init */
NULL,
};
#ifdef TR_ICON_SUPPORTED
type = GTK_TYPE_STATUS_ICON;
#else
type = G_TYPE_OBJECT;
#endif
type = g_type_register_static( type, "TrIcon", &info, 0 );
}
return type;
}
static void
tr_icon_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
{
GObjectClass * gobject_class;
TrIconClass * tricon_class;
GParamSpec * pspec;
gobject_class = G_OBJECT_CLASS( g_class );
gobject_class->set_property = tr_icon_set_property;
gobject_class->get_property = tr_icon_get_property;
gobject_class->dispose = tr_icon_dispose;
pspec = g_param_spec_boolean( "icon", "Icon",
"Icon has been set from default window icon.",
TRUE, G_PARAM_CONSTRUCT|G_PARAM_READWRITE );
g_object_class_install_property( gobject_class, PROP_ICON, pspec );
pspec = g_param_spec_boolean( "docked", "Docked",
"Icon is docked in a system tray.",
FALSE, G_PARAM_READABLE );
g_object_class_install_property( gobject_class, PROP_DOCKED, pspec );
pspec = g_param_spec_int( "activate-action", "Activate action",
"The action id to signal when icon is activated.",
G_MININT, G_MAXINT, -1, G_PARAM_READWRITE );
g_object_class_install_property( gobject_class, PROP_CLICK, pspec );
tricon_class = TR_ICON_CLASS( g_class );
tricon_class->actionsig =
g_signal_new( "action", G_TYPE_FROM_CLASS( g_class ),
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT );
}
static void
tr_icon_init( GTypeInstance * instance, gpointer g_class SHUTUP )
{
TrIcon * self = ( TrIcon * )instance;
self->clickact = -1;
self->menu = NULL;
self->actions = NULL;
self->disposed = FALSE;
#ifdef TR_ICON_SUPPORTED
self->menu = gtk_menu_new();
gtk_widget_show( self->menu );
g_signal_connect( self, "activate", G_CALLBACK( clicked ), NULL );
g_signal_connect( self, "popup-menu", G_CALLBACK( popup ), NULL );
#endif
}
static void
tr_icon_set_property( GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
TrIcon * self = ( TrIcon * )object;
if( self->disposed )
{
return;
}
switch( property_id )
{
case PROP_ICON:
#ifdef TR_ICON_SUPPORTED
if( g_value_get_boolean( value ) )
{
GList * icons = gtk_window_get_default_icon_list();
if( NULL != icons && NULL != icons->data )
{
gtk_status_icon_set_from_pixbuf( GTK_STATUS_ICON( self ),
icons->data );
}
g_list_free( icons );
}
else
{
gtk_status_icon_set_from_pixbuf( GTK_STATUS_ICON( self ),
NULL );
}
#endif
break;
case PROP_CLICK:
self->clickact = g_value_get_int( value );
break;
case PROP_DOCKED:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
break;
}
}
static void
tr_icon_get_property( GObject * object, guint property_id,
GValue * value, GParamSpec * pspec )
{
TrIcon * self = ( TrIcon * )object;
#ifdef TR_ICON_SUPPORTED
GtkStatusIcon * icon;
#endif
if( self->disposed )
{
return;
}
switch( property_id )
{
case PROP_ICON:
#ifdef TR_ICON_SUPPORTED
icon = GTK_STATUS_ICON( self );
if( GTK_IMAGE_PIXBUF == gtk_status_icon_get_storage_type( icon ) &&
NULL != gtk_status_icon_get_pixbuf( icon ) )
{
g_value_set_boolean( value, TRUE );
}
else
#endif
{
g_value_set_boolean( value, FALSE );
}
break;
case PROP_DOCKED:
#ifdef TR_ICON_SUPPORTED
if( gtk_status_icon_is_embedded( GTK_STATUS_ICON( self ) ) )
{
g_value_set_boolean( value, TRUE );
}
else
#endif
{
g_value_set_boolean( value, FALSE );
}
break;
case PROP_CLICK:
g_value_set_int( value, self->clickact );
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
break;
}
}
static void
tr_icon_dispose( GObject * obj )
{
TrIcon * self = ( TrIcon * )obj;
GObjectClass * parent;
if( self->disposed )
{
return;
}
self->disposed = TRUE;
g_list_foreach( self->actions, ( GFunc )action_free, NULL );
g_list_free( self->actions );
/* Chain up to the parent class */
parent = g_type_class_peek( g_type_parent( TR_ICON_TYPE ) );
parent->dispose( obj );
}
TrIcon *
gpointer
tr_icon_new( void )
{
return g_object_new( TR_ICON_TYPE, NULL );
return NULL;
}
gboolean
tr_icon_docked( TrIcon * self )
#else
static void
activated ( GtkStatusIcon * self UNUSED,
gpointer user_data UNUSED )
{
gboolean ret;
action_activate ("toggle-main-window");
}
g_object_get( self, "docked", &ret, NULL );
static void
popup ( GtkStatusIcon * self,
guint button,
guint when,
gpointer data UNUSED )
{
GtkWidget * w = action_get_widget( "/icon-popup" );
gtk_menu_popup (GTK_MENU(w), NULL, NULL,
gtk_status_icon_position_menu,
self, button, when );
}
gpointer
tr_icon_new( void )
{
GtkStatusIcon * ret = gtk_status_icon_new_from_stock ("ICON_TRANSMISSION");
g_signal_connect( ret, "activate", G_CALLBACK( activated ), NULL );
g_signal_connect( ret, "popup-menu", G_CALLBACK( popup ), NULL );
return ret;
}
void
tr_icon_action_add( TrIcon * self, int id, int flags, const char * name,
const char * icon )
{
struct action * act;
GtkWidget * item;
TR_IS_ICON( self );
if( self->disposed )
{
return;
}
act = action_new( id, flags, name, icon );
if( NULL != self->menu )
{
if( ACTF_MENU & flags && ACTF_ALWAYS & flags )
{
item = action_makemenu( act, ITEM_ACTION, NULL, NULL, 0,
G_CALLBACK( itemclick ), self );
gtk_menu_shell_append( GTK_MENU_SHELL( self->menu ), item );
act->menu = item;
}
else if( ACTF_SEPARATOR & flags )
{
item = gtk_separator_menu_item_new();
gtk_widget_show( item );
gtk_menu_shell_append( GTK_MENU_SHELL( self->menu ), item );
}
}
self->actions = g_list_append( self->actions, act );
}
static void
itemclick( GObject * obj, gpointer data )
{
TrIcon * self;
struct action * act;
TR_IS_ICON( data );
self = TR_ICON( data );
act = g_object_get_data( obj, ITEM_ACTION );
emitaction( self, act->id );
}
static void
emitaction( TrIcon * self, int id )
{
TrIconClass * class;
class = g_type_class_peek( TR_ICON_TYPE );
g_signal_emit( self, class->actionsig, 0, id );
}
#ifdef TR_ICON_SUPPORTED
static void
clicked( TrIcon * self, gpointer data SHUTUP )
{
TR_IS_ICON( self );
if( self->disposed || 0 > self->clickact )
{
return;
}
emitaction( self, self->clickact );
}
static void
popup( TrIcon * self, guint button, guint when, gpointer data SHUTUP )
{
TR_IS_ICON( self );
if( self->disposed )
{
return;
}
gtk_menu_popup( GTK_MENU( self->menu ), NULL, NULL,
gtk_status_icon_position_menu, self, button, when );
}
#endif /* TR_ICON_SUPPORTED */
#endif

View File

@ -27,69 +27,13 @@
#include <gtk/gtk.h>
#if GTK_MAJOR_VERSION > 2 || \
( GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 10 )
#define TR_ICON_SUPPORTED
#define tr_icon_supported() (TRUE)
#if GTK_CHECK_VERSION(2,10,0)
#define STATUS_ICON_SUPPORTED
#define status_icon_supported() (TRUE)
#else
#define tr_icon_supported() (FALSE)
#define status_icon_supported() (FALSE)
#endif
#define TR_ICON_TYPE ( tr_icon_get_type() )
#define TR_ICON( obj ) \
( G_TYPE_CHECK_INSTANCE_CAST( (obj), TR_ICON_TYPE, TrIcon ) )
#define TR_ICON_CLASS( class ) \
( G_TYPE_CHECK_CLASS_CAST( (class), TR_ICON_TYPE, TrIconClass ) )
#define TR_IS_ICON( obj ) \
( G_TYPE_CHECK_INSTANCE_TYPE( (obj), TR_ICON_TYPE ) )
#define TR_IS_ICON_CLASS( class ) \
( G_TYPE_CHECK_CLASS_TYPE( (class), TR_ICON_TYPE ) )
#define TR_ICON_GET_CLASS( obj ) \
( G_TYPE_INSTANCE_GET_CLASS( (obj), TR_ICON_TYPE, TrIconClass ) )
typedef struct _TrIcon TrIcon;
typedef struct _TrIconClass TrIconClass;
/* treat the contents of this structure as private */
struct _TrIcon
{
#ifdef TR_ICON_SUPPORTED
GtkStatusIcon parent;
#else
GObject parent;
#endif
GtkWidget * menu;
GList * actions;
int clickact;
gboolean disposed;
};
struct _TrIconClass
{
#ifdef TR_ICON_SUPPORTED
GtkStatusIconClass parent;
#else
GObjectClass parent;
#endif
int actionsig;
};
GType
tr_icon_get_type( void );
TrIcon *
tr_icon_new( void );
gboolean
tr_icon_docked( TrIcon * icon );
void
tr_icon_action_add( TrIcon * icon, int id, int flags, const char * label,
const char * stock );
gpointer tr_icon_new( void );
#endif

View File

@ -127,7 +127,7 @@ defs[] =
/* PREF_ID_ICON */
{ "use-tray-icon", G_TYPE_BOOLEAN,
( tr_icon_supported() ? PR_ENABLED : PR_DISABLED ), NULL,
( status_icon_supported() ? PR_ENABLED : PR_DISABLED ), NULL,
N_("Display an _icon in the system tray"),
N_("Use a system tray / dock / notification area icon") },

View File

@ -647,7 +647,7 @@ tr_torrent_status_str ( TrTorrent * gtor )
const int tpeers = MAX (st->peersTotal, 0);
const int upeers = MAX (st->peersUploading, 0);
const int eta = st->eta;
const double prog = st->progress * 100.0; /* [0...100] */
const double prog = st->percentDone * 100.0; /* [0...100] */
const int status = st->status;
if( TR_STATUS_CHECK_WAIT & status )
@ -672,13 +672,20 @@ tr_torrent_status_str ( TrTorrent * gtor )
g_free(timestr);
}
}
else if(TR_STATUS_SEED & status)
else if( TR_STATUS_SEED & status )
{
top = g_strdup_printf(
ngettext( "Seeding, uploading to %d of %d peer",
"Seeding, uploading to %d of %d peers", tpeers ),
upeers, tpeers );
}
else if( TR_STATUS_DONE & status )
{
top = g_strdup_printf(
ngettext( "Uploading to %d of %d peer",
"Uploading to %d of %d peers", tpeers ),
upeers, tpeers );
}
else if( TR_STATUS_STOPPING & status )
{
top = g_strdup( _("Stopping...") );

View File

@ -29,408 +29,35 @@
#include "transmission.h"
#include "actions.h"
#include "hig.h"
#include "tr_cell_renderer_progress.h"
#include "tr_core.h"
#include "tr_torrent.h"
#include "tr_window.h"
#include "util.h"
#define ITEM_ACTION "tr-window-item-action"
enum
typedef struct
{
PROP_MODEL = 1,
PROP_SELECTION,
PROP_DOUBLECLICK,
PROP_DRAG,
};
GtkWidget * scroll;
GtkWidget * view;
GtkWidget * status;
GtkTreeSelection * selection;
GtkCellRenderer * namerend;
}
PrivateData;
static void
tr_window_init( GTypeInstance * instance, gpointer g_class );
static void
tr_window_set_property( GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec );
static void
tr_window_get_property( GObject * object, guint property_id,
GValue * value, GParamSpec * pspec);
static void
tr_window_class_init( gpointer g_class, gpointer g_class_data );
static void
tr_window_dispose( GObject * obj );
static GtkTreeView *
makeview( TrWindow * self );
static void
stylekludge( GObject * obj, GParamSpec * spec, gpointer data );
static void
fixbuttons( GtkTreeSelection *sel, TrWindow * self );
static void
formatname( GtkTreeViewColumn * col, GtkCellRenderer * rend,
GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
static void
formatprog( GtkTreeViewColumn * col, GtkCellRenderer * rend,
GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
static gboolean
listclick( GtkWidget * view, GdkEventButton * event, gpointer data );
static gboolean
listpopup( GtkWidget * view SHUTUP, gpointer data );
static void
popupmenu( TrWindow * self, GdkEventButton * event );
static void
itemclick( GObject * obj, gpointer data );
static void
doubleclick( GtkWidget * view, GtkTreePath * path,
GtkTreeViewColumn * col SHUTUP, gpointer data );
static void
emitaction( TrWindow * self, int id );
static void
orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
gpointer data );
static void
istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
gpointer data );
#define PRIVATE_DATA_KEY "private-data"
GType
tr_window_get_type( void )
PrivateData*
get_private_data( TrWindow * w )
{
static GType type = 0;
if( 0 == type )
{
static const GTypeInfo info =
{
sizeof( TrWindowClass ),
NULL, /* base_init */
NULL, /* base_finalize */
tr_window_class_init, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
sizeof( TrWindow ),
0, /* n_preallocs */
tr_window_init, /* instance_init */
NULL,
};
type = g_type_register_static( GTK_TYPE_WINDOW, "TrWindow", &info, 0 );
}
return type;
return (PrivateData*) g_object_get_data (G_OBJECT(w), PRIVATE_DATA_KEY);
}
static void
tr_window_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
{
GObjectClass * gobject_class;
TrWindowClass * trwindow_class;
GParamSpec * pspec;
gobject_class = G_OBJECT_CLASS( g_class );
gobject_class->set_property = tr_window_set_property;
gobject_class->get_property = tr_window_get_property;
gobject_class->dispose = tr_window_dispose;
pspec = g_param_spec_object( "model", "Model",
"The GtkTreeModel for the list view.",
GTK_TYPE_TREE_MODEL, G_PARAM_READWRITE );
g_object_class_install_property( gobject_class, PROP_MODEL, pspec );
pspec = g_param_spec_object( "selection", "Selection",
"The GtkTreeSelection for the list view.",
GTK_TYPE_TREE_SELECTION, G_PARAM_READABLE );
g_object_class_install_property( gobject_class, PROP_SELECTION, pspec );
pspec = g_param_spec_int( "double-click-action", "Double-click action",
"The action id to signal on a double click.",
G_MININT, G_MAXINT, -1, G_PARAM_READWRITE );
g_object_class_install_property( gobject_class, PROP_DOUBLECLICK, pspec );
pspec = g_param_spec_object( "drag-widget", "Drag widget",
"The GtkWidget used for drag-and-drop.",
GTK_TYPE_WIDGET, G_PARAM_READABLE );
g_object_class_install_property( gobject_class, PROP_DRAG, pspec );
trwindow_class = TR_WINDOW_CLASS( g_class );
trwindow_class->actionsig =
g_signal_new( "action", G_TYPE_FROM_CLASS( g_class ),
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT );
}
static void
tr_window_init( GTypeInstance * instance, gpointer g_class SHUTUP )
{
TrWindow * self = ( TrWindow * )instance;
GtkWidget * vbox, * scroll, * status, * tools, * menu, * file, * item;
vbox = gtk_vbox_new( FALSE, 0 );
scroll = gtk_scrolled_window_new( NULL, NULL );
status = gtk_statusbar_new();
tools = gtk_toolbar_new();
menu = gtk_menu_bar_new();
file = gtk_menu_new();
item = gtk_menu_item_new_with_mnemonic( _("_File") );
self->scroll = GTK_SCROLLED_WINDOW( scroll );
self->view = makeview( self );
self->status = GTK_STATUSBAR( status );
self->toolbar = GTK_TOOLBAR( tools );
self->menu = GTK_MENU_SHELL( file );
/* this should have been set by makeview() */
g_assert( NULL != self->namerend );
self->doubleclick = -1;
self->actions = NULL;
self->accel = gtk_accel_group_new();
self->stupidpopuphack = NULL;
self->disposed = FALSE;
gtk_window_add_accel_group( GTK_WINDOW( self ), self->accel );
gtk_menu_set_accel_group( GTK_MENU( self->menu ), self->accel );
gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), file );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
gtk_box_pack_start( GTK_BOX( vbox ), menu, FALSE, FALSE, 0 );
gtk_toolbar_set_tooltips( self->toolbar, TRUE );
gtk_toolbar_set_show_arrow( self->toolbar, FALSE );
gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->toolbar ),
FALSE, FALSE, 0 );
gtk_container_add( GTK_CONTAINER( scroll ), GTK_WIDGET( self->view ) );
gtk_box_pack_start( GTK_BOX( vbox ), scroll, TRUE, TRUE, 0 );
gtk_statusbar_push( self->status, 0, "" );
gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->status ),
FALSE, FALSE, 0 );
gtk_container_set_focus_child( GTK_CONTAINER( vbox ), scroll );
gtk_widget_show_all( vbox );
gtk_container_add( GTK_CONTAINER( self ), vbox );
gtk_window_set_title( GTK_WINDOW( self ), g_get_application_name());
gtk_window_set_role( GTK_WINDOW( self ), "tr-main" );
}
static void
tr_window_set_property( GObject * object, guint property_id,
const GValue * value SHUTUP, GParamSpec * pspec)
{
TrWindow * self = ( TrWindow * )object;
if( self->disposed )
{
return;
}
switch( property_id )
{
case PROP_MODEL:
gtk_tree_view_set_model( self->view, g_value_get_object( value ) );
break;
case PROP_DOUBLECLICK:
self->doubleclick = g_value_get_int( value );
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
break;
}
}
static void
tr_window_get_property( GObject * object, guint property_id,
GValue * value SHUTUP, GParamSpec * pspec )
{
TrWindow * self = ( TrWindow * )object;
if( self->disposed )
{
return;
}
switch( property_id )
{
case PROP_MODEL:
g_value_set_object( value, gtk_tree_view_get_model( self->view ) );
break;
case PROP_SELECTION:
g_value_set_object( value,
gtk_tree_view_get_selection( self->view ) );
break;
case PROP_DOUBLECLICK:
g_value_set_int( value, self->doubleclick );
break;
case PROP_DRAG:
g_value_set_object( value, self->view );
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
break;
}
}
static void
tr_window_dispose( GObject * obj )
{
TrWindow * self = ( TrWindow * )obj;
GObjectClass * parent;
if( self->disposed )
{
return;
}
self->disposed = TRUE;
g_list_foreach( self->actions, ( GFunc )action_free, NULL );
g_list_free( self->actions );
g_object_unref( self->accel );
if( NULL != self->stupidpopuphack )
{
gtk_widget_destroy( self->stupidpopuphack );
}
self->stupidpopuphack = NULL;
/* Chain up to the parent class */
parent = g_type_class_peek( g_type_parent( TR_WINDOW_TYPE ) );
parent->dispose( obj );
}
GtkWidget *
tr_window_new( void )
{
return g_object_new( TR_WINDOW_TYPE, NULL );
}
void
tr_window_action_add( TrWindow * self, int id, int flags, const char * name,
const char * icon, const char * description, guint key )
{
struct action * act;
GtkWidget * sep;
TR_IS_WINDOW( self );
if( self->disposed )
{
return;
}
act = action_new( id, flags, name, icon );
if( ACTF_TOOL & flags )
{
act->tool = action_maketool( act, ITEM_ACTION,
G_CALLBACK( itemclick ), self );
gtk_tool_item_set_tooltip( GTK_TOOL_ITEM( act->tool ),
self->toolbar->tooltips, description, "" );
gtk_toolbar_insert( self->toolbar, GTK_TOOL_ITEM( act->tool ), -1 );
}
if( ACTF_MENU & flags )
{
act->menu = action_makemenu( act, ITEM_ACTION, self->accel,
"<transmission-mainwind>/file", key,
G_CALLBACK( itemclick ), self );
gtk_menu_shell_append( self->menu, act->menu );
}
if( ACTF_SEPARATOR & flags )
{
sep = gtk_separator_menu_item_new();
gtk_widget_show( sep );
gtk_menu_shell_append( self->menu, sep );
}
self->actions = g_list_append( self->actions, act );
}
void
tr_window_update( TrWindow * self, float downspeed, float upspeed )
{
char * downstr, * upstr, * str;
TR_IS_WINDOW( self );
if( self->disposed )
{
return;
}
/* update the status bar */
downstr = readablespeed ( downspeed );
upstr = readablespeed ( upspeed );
str = g_strdup_printf( _(" Total DL: %s Total UL: %s"),
downstr, upstr );
g_free( downstr );
g_free( upstr );
gtk_statusbar_pop( self->status, 0 );
gtk_statusbar_push( self->status, 0, str );
g_free( str );
/* the selection's status may have changed so update the buttons */
fixbuttons( NULL, self );
}
void
tr_window_show( TrWindow * self )
{
TR_IS_WINDOW( self );
sizingmagic( GTK_WINDOW( self ), self->scroll,
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
g_object_set( self->namerend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
gtk_widget_show( GTK_WIDGET( self ) );
}
static GtkTreeView *
makeview( TrWindow * self )
{
GtkWidget * view;
GtkTreeViewColumn * col;
GtkTreeSelection * sel;
GtkCellRenderer * namerend, * progrend;
char * str;
TR_IS_WINDOW( self );
view = gtk_tree_view_new();
namerend = gtk_cell_renderer_text_new();
self->namerend = G_OBJECT( namerend );
/* note that this renderer is set to ellipsize, just not here */
col = gtk_tree_view_column_new_with_attributes( _("Name"), namerend,
NULL );
gtk_tree_view_column_set_cell_data_func( col, namerend, formatname,
NULL, NULL );
gtk_tree_view_column_set_expand( col, TRUE );
gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
progrend = tr_cell_renderer_progress_new();
/* this string is only used to determine the size of the progress bar */
str = g_markup_printf_escaped( "<big>%s</big>", _(" fnord fnord ") );
g_object_set( progrend, "bar-sizing", str, NULL );
g_free(str);
col = gtk_tree_view_column_new_with_attributes( _("Progress"), progrend,
NULL);
gtk_tree_view_column_set_cell_data_func( col, progrend, formatprog,
NULL, NULL );
gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
/* XXX this shouldn't be necessary */
g_signal_connect( view, "notify::style",
G_CALLBACK( stylekludge ), progrend );
gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
GTK_SELECTION_MULTIPLE );
g_signal_connect( G_OBJECT( sel ), "changed",
G_CALLBACK( fixbuttons ), self );
g_signal_connect( G_OBJECT( view ), "button-press-event",
G_CALLBACK( listclick ), self );
g_signal_connect( G_OBJECT( view ), "popup-menu",
G_CALLBACK( listpopup ), self );
g_signal_connect( G_OBJECT( view ), "row-activated",
G_CALLBACK( doubleclick ), self );
return GTK_TREE_VIEW( view );
}
/***
****
***/
/* kludge to have the progress bars notice theme changes */
static void
@ -444,49 +71,6 @@ stylekludge( GObject * obj, GParamSpec * spec, gpointer data )
}
}
/* disable buttons and menuitems the user shouldn't be able to click on */
static void
fixbuttons( GtkTreeSelection *sel, TrWindow * self ) {
gboolean selected, avail;
GList * ii;
int status;
struct action * act;
TR_IS_WINDOW( self );
if( self->disposed )
{
return;
}
if( NULL == sel )
{
sel = gtk_tree_view_get_selection( self->view );
}
status = 0;
gtk_tree_selection_selected_foreach( sel, orstatus, &status );
selected = ( 0 < gtk_tree_selection_count_selected_rows( sel ) );
for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
{
act = ii->data;
if( ACTF_ALWAYS & act->flags )
{
continue;
}
avail = ACT_ISAVAIL( act->flags, status );
if( ACTF_TOOL & act->flags )
{
g_assert( NULL != act->tool );
gtk_widget_set_sensitive( act->tool, selected && avail );
}
if( ACTF_MENU & act->flags )
{
g_assert( NULL != act->menu );
gtk_widget_set_sensitive( act->menu, selected && avail );
}
}
}
static void
formatname( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
GtkTreeModel * model, GtkTreeIter * iter, gpointer data SHUTUP )
@ -546,7 +130,7 @@ formatprog( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
gfloat prog, dl, ul;
guint64 down, up;
gtk_tree_model_get( model, iter, MC_PROG, &prog, MC_DRATE, &dl,
gtk_tree_model_get( model, iter, MC_PROG_D, &prog, MC_DRATE, &dl,
MC_URATE, &ul, MC_DOWN, &down, MC_UP, &up, -1 );
prog = MAX( prog, 0.0 );
prog = MIN( prog, 1.0 );
@ -570,199 +154,156 @@ formatprog( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
g_free( marked );
}
/* show a popup menu for a right-click on the list */
static gboolean
listclick( GtkWidget * view, GdkEventButton * event, gpointer data )
static void
on_popup_menu ( GtkWidget * self UNUSED, GdkEventButton * event )
{
TrWindow * self;
GtkTreeSelection * sel;
GtkTreePath * path;
GtkTreeModel * model;
GtkTreeIter iter;
int status;
TrTorrent * tor, * issel;
GtkWidget * menu = action_get_widget ( "/main-window-popup" );
gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL,
(event ? event->button : 0),
(event ? event->time : 0));
}
if( GDK_BUTTON_PRESS != event->type || 3 != event->button )
{
return FALSE;
}
static void
view_row_activated ( GtkTreeView * tree_view UNUSED,
GtkTreePath * path UNUSED,
GtkTreeViewColumn * column UNUSED,
gpointer user_data UNUSED )
{
action_activate( "show-torrent-inspector" );
}
TR_IS_WINDOW( data );
self = TR_WINDOW( data );
if( self->disposed )
{
return FALSE;
}
static GtkWidget*
makeview( PrivateData * p )
{
GtkWidget * view;
GtkTreeViewColumn * col;
GtkTreeSelection * sel;
GtkCellRenderer * namerend, * progrend;
char * str;
view = gtk_tree_view_new();
p->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(view) );
namerend = gtk_cell_renderer_text_new();
p->namerend = namerend;
/* note that this renderer is set to ellipsize, just not here */
col = gtk_tree_view_column_new_with_attributes( _("Name"), namerend,
NULL );
gtk_tree_view_column_set_cell_data_func( col, namerend, formatname,
NULL, NULL );
gtk_tree_view_column_set_expand( col, TRUE );
gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
progrend = tr_cell_renderer_progress_new();
/* this string is only used to determine the size of the progress bar */
str = g_markup_printf_escaped( "<big>%s</big>", _(" fnord fnord ") );
g_object_set( progrend, "bar-sizing", str, NULL );
g_free(str);
col = gtk_tree_view_column_new_with_attributes( _("Progress"), progrend,
NULL);
gtk_tree_view_column_set_cell_data_func( col, progrend, formatprog,
NULL, NULL );
gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
/* XXX this shouldn't be necessary */
g_signal_connect( view, "notify::style",
G_CALLBACK( stylekludge ), progrend );
gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
model = gtk_tree_view_get_model( GTK_TREE_VIEW( view ) );
gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
GTK_SELECTION_MULTIPLE );
/* find what row, if any, the user clicked on */
if( gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW( view ),
event->x, event->y, &path,
NULL, NULL, NULL ) )
{
if( gtk_tree_model_get_iter( model, &iter, path ) )
{
/* get torrent and status for the right-clicked row */
gtk_tree_model_get( model, &iter, MC_TORRENT, &tor,
MC_STAT, &status, -1 );
issel = tor;
gtk_tree_selection_selected_foreach( sel, istorsel, &issel );
g_object_unref( tor );
/* if the clicked row isn't selected, select only it */
if( NULL != issel )
{
gtk_tree_selection_unselect_all( sel );
gtk_tree_selection_select_iter( sel, &iter );
}
}
gtk_tree_path_free( path );
}
else
{
gtk_tree_selection_unselect_all( sel );
}
g_signal_connect( view, "popup-menu",
G_CALLBACK(on_popup_menu), NULL );
g_signal_connect( view, "button-press-event",
G_CALLBACK(on_tree_view_button_pressed), on_popup_menu);
g_signal_connect( view, "row-activated",
G_CALLBACK(view_row_activated), NULL);
popupmenu( self, event );
return TRUE;
}
static gboolean
listpopup( GtkWidget * view SHUTUP, gpointer data )
{
popupmenu( TR_WINDOW( data ), NULL );
return TRUE;
return view;
}
static void
popupmenu( TrWindow * self, GdkEventButton * event )
realized_cb ( GtkWidget * wind, gpointer unused UNUSED )
{
GtkTreeSelection * sel;
int count, status;
GtkWidget * menu, * item;
GList * ii;
struct action * act;
TR_IS_WINDOW( self );
if( self->disposed )
{
return;
}
sel = gtk_tree_view_get_selection( self->view );
count = gtk_tree_selection_count_selected_rows( sel );
menu = gtk_menu_new();
if( NULL != self->stupidpopuphack )
{
gtk_widget_destroy( self->stupidpopuphack );
}
self->stupidpopuphack = menu;
status = 0;
gtk_tree_selection_selected_foreach( sel, orstatus, &status );
for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
{
act = ii->data;
if( ACTF_SEPARATOR & act->flags )
{
item = gtk_separator_menu_item_new();
gtk_widget_show( item );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
}
else if( ACTF_MENU & act->flags && ACT_ISAVAIL( act->flags, status ) )
{
item = action_makemenu( act, ITEM_ACTION, NULL, NULL, 0,
G_CALLBACK( itemclick ), self );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
}
}
gtk_widget_show( menu );
gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
( NULL == event ? 0 : event->button ),
gdk_event_get_time( (GdkEvent*)event ) );
PrivateData * p = get_private_data( GTK_WINDOW( wind ) );
sizingmagic( GTK_WINDOW(wind), GTK_SCROLLED_WINDOW( p->scroll ),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
g_object_set( p->namerend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
}
static void
itemclick( GObject * obj, gpointer data )
/***
**** PUBLIC
***/
GtkWidget *
tr_window_new( GtkUIManager * ui_manager )
{
TrWindow * self;
struct action * act;
PrivateData * p = g_new( PrivateData, 1 );
GtkWidget *vbox, *w, *self;
TR_IS_WINDOW( data );
self = TR_WINDOW( data );
act = g_object_get_data( obj, ITEM_ACTION );
/* make the window */
self = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_object_set_data_full(G_OBJECT(self), PRIVATE_DATA_KEY, p, g_free );
gtk_window_set_title( GTK_WINDOW( self ), g_get_application_name());
gtk_window_set_role( GTK_WINDOW( self ), "tr-main" );
gtk_window_add_accel_group (GTK_WINDOW(self),
gtk_ui_manager_get_accel_group (ui_manager));
g_signal_connect( self, "realize", G_CALLBACK(realized_cb), NULL);
emitaction( self, act->id );
/* window's main container */
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER(self), vbox);
/* main menu */
w = action_get_widget( "/main-window-menu" );
gtk_box_pack_start( GTK_BOX(vbox), w, FALSE, FALSE, 0 );
/* toolbar */
w = action_get_widget( "/main-window-toolbar" );
gtk_box_pack_start( GTK_BOX(vbox), w, FALSE, FALSE, 0 );
/* workarea */
p->view = makeview( p );
w = p->scroll = gtk_scrolled_window_new( NULL, NULL );
gtk_container_add( GTK_CONTAINER(w), p->view );
gtk_box_pack_start_defaults( GTK_BOX(vbox), w );
gtk_container_set_focus_child( GTK_CONTAINER( vbox ), w );
/* statusbar */
w = p->status = gtk_statusbar_new ();
gtk_statusbar_push( GTK_STATUSBAR(w), 0, "" );
gtk_box_pack_start( GTK_BOX(vbox), w, FALSE, FALSE, 0 );
/* show all but the window */
gtk_widget_show_all( vbox );
return self;
}
static void
doubleclick( GtkWidget * view SHUTUP, GtkTreePath * path,
GtkTreeViewColumn * col SHUTUP, gpointer data )
void
tr_window_update( TrWindow * self, float downspeed, float upspeed )
{
TrWindow * self;
GtkTreeSelection * sel;
PrivateData * p = get_private_data( self );
GtkStatusbar * status = GTK_STATUSBAR( p->status );
TR_IS_WINDOW( data );
self = TR_WINDOW( data );
if( self->disposed || 0 > self->doubleclick )
{
return;
}
sel = gtk_tree_view_get_selection( self->view );
gtk_tree_selection_select_path( sel, path );
emitaction( self, self->doubleclick );
/* update the status bar */
char * downstr = readablespeed ( downspeed );
char * upstr = readablespeed ( upspeed );
char * str = g_strdup_printf( _(" Total DL: %s Total UL: %s"),
downstr, upstr );
g_free( downstr );
g_free( upstr );
gtk_statusbar_pop( status, 0 );
gtk_statusbar_push( status, 0, str );
g_free( str );
}
static void
emitaction( TrWindow * self, int id )
GtkTreeSelection*
tr_window_get_selection ( TrWindow * w )
{
TrWindowClass * class;
TR_IS_WINDOW( self );
if( self->disposed )
{
return;
}
class = g_type_class_peek( TR_WINDOW_TYPE );
g_signal_emit( self, class->actionsig, 0, id );
}
/* use with gtk_tree_selection_selected_foreach to | status of selected rows */
static void
orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
gpointer data )
{
int * allstatus, thisstatus;
allstatus = data;
gtk_tree_model_get( model, iter, MC_STAT, &thisstatus, -1 );
*allstatus |= thisstatus;
}
/* data should be a TrTorrent**, will set torrent to NULL if it's selected */
static void
istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
gpointer data )
{
TrTorrent ** torref, * tor;
torref = data;
if( NULL != *torref )
{
gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
if( tor == *torref )
{
*torref = NULL;
}
g_object_unref( tor );
}
return get_private_data(w)->selection;
}

View File

@ -25,67 +25,15 @@
#ifndef TR_WINDOW_H
#define TR_WINDOW_H
#include <glib-object.h>
#include <gtk/gtk.h>
#define TR_WINDOW_TYPE ( tr_window_get_type() )
typedef GtkWindow TrWindow;
#define TR_WINDOW( obj ) \
( G_TYPE_CHECK_INSTANCE_CAST( (obj), TR_WINDOW_TYPE, TrWindow ) )
GtkTreeSelection * tr_window_get_selection( TrWindow* wind );
#define TR_WINDOW_CLASS( class ) \
( G_TYPE_CHECK_CLASS_CAST( (class), TR_WINDOW_TYPE, TrWindowClass ) )
#define TR_IS_WINDOW( obj ) \
( G_TYPE_CHECK_INSTANCE_TYPE( (obj), TR_WINDOW_TYPE ) )
#define TR_IS_WINDOW_CLASS( class ) \
( G_TYPE_CHECK_CLASS_TYPE( (class), TR_WINDOW_TYPE ) )
#define TR_WINDOW_GET_CLASS( obj ) \
( G_TYPE_INSTANCE_GET_CLASS( (obj), TR_WINDOW_TYPE, TrWindowClass ) )
typedef struct _TrWindow TrWindow;
typedef struct _TrWindowClass TrWindowClass;
/* treat the contents of this structure as private */
struct _TrWindow
{
GtkWindow parent;
GtkScrolledWindow * scroll;
GtkTreeView * view;
GtkStatusbar * status;
GtkToolbar * toolbar;
GtkMenuShell * menu;
GObject * namerend;
int doubleclick;
GList * actions;
GtkAccelGroup * accel;
GtkWidget * stupidpopuphack;
gboolean disposed;
};
struct _TrWindowClass
{
GtkWindowClass parent;
int actionsig;
};
GType
tr_window_get_type( void );
GtkWidget *
tr_window_new( void );
void
tr_window_action_add( TrWindow * wind, int id, int flags, const char * name,
const char * icon, const char * description, guint key );
GtkWidget * tr_window_new( GtkUIManager* );
void
tr_window_update( TrWindow * wind, float downspeed, float upspeed );
/* some magic to show the window with a nice initial size */
void
tr_window_show( TrWindow * wind );
#endif

View File

@ -77,8 +77,9 @@ The
program was written by
.An Josh Elsasser Aq josh@elsasser.org ,
.An Eric Petit Aq titer@m0k.org ,
.An Mitchell Livingston Aq livings124@gmail.com ,
and
.An Mitchell Livingston Aq livings124@gmail.com .
.An Charles Kerr Aq charles@rebelbase.com .
.Sh SEE ALSO
.Xr transmissioncli 1 ,
.Xr transmission-daemon 1 ,

66
gtk/ui.h Normal file
View File

@ -0,0 +1,66 @@
const char * fallback_ui_file =
"<ui>\n"
" <menubar name='main-window-menu'>\n"
" <menu action='file-menu'>\n"
" <menuitem action='add-torrent'/>\n"
" <menuitem action='start-torrent'/>\n"
" <menuitem action='stop-torrent'/>\n"
" <menuitem action='recheck-torrent'/>\n"
" <menuitem action='remove-torrent'/>\n"
" <separator/>\n"
" <menuitem action='create-torrent'/>\n"
" <separator/>\n"
" <menuitem action='close'/>\n"
" <menuitem action='quit'/>\n"
" </menu>\n"
" <menu action='edit-menu'>\n"
" <menuitem action='show-torrent-inspector'/>\n"
" <menuitem action='edit-preferences'/>\n"
" </menu>\n"
" <menu action='help-menu'>\n"
" <menuitem action='show-debug-window'/>\n"
" <separator/>\n"
" <menuitem action='show-about-dialog'/>\n"
" </menu>\n"
" </menubar>\n"
"\n"
" <toolbar name='main-window-toolbar'>\n"
" <toolitem action='add-torrent'/>\n"
" <toolitem action='start-torrent'/>\n"
" <toolitem action='stop-torrent'/>\n"
" <toolitem action='remove-torrent'/>\n"
" <separator/>\n"
" <toolitem action='show-torrent-inspector'/>\n"
" </toolbar>\n"
"\n"
" <popup name='main-window-popup'>\n"
" <menuitem action='show-torrent-inspector'/>\n"
" <separator/>\n"
" <menuitem action='start-torrent'/>\n"
" <menuitem action='stop-torrent'/>\n"
" <menuitem action='recheck-torrent'/>\n"
" <separator/>\n"
" <menuitem action='remove-torrent'/>\n"
" </popup>\n"
"\n"
" <popup name='icon-popup'>\n"
" <menuitem action='add-torrent'/>\n"
" <separator/>\n"
" <menuitem action='toggle-main-window'/>\n"
" <menuitem action='show-debug-window'/>\n"
" <menuitem action='show-about-dialog'/>\n"
" <separator/>\n"
" <menuitem action='quit'/>\n"
" </popup>\n"
"\n"
" <popup name='file-popup'>\n"
" <menu action='priority-menu'>\n"
" <menuitem action='priority-high'/>\n"
" <menuitem action='priority-normal'/>\n"
" <menuitem action='priority-low'/>\n"
" <menuitem action='priority-dnd'/>\n"
" </menu>\n"
" </popup>\n"
"\n"
"</ui>";

View File

@ -319,128 +319,38 @@ getdownloaddir( void )
return dir;
}
/**
* don't use more than 50% the height of the screen, nor 80% the width.
* but don't be too small, either -- set the minimums to 500 x 300
*/
void
sizingmagic( GtkWindow * wind, GtkScrolledWindow * scroll,
GtkPolicyType hscroll, GtkPolicyType vscroll )
sizingmagic( GtkWindow * wind,
GtkScrolledWindow * scroll,
GtkPolicyType hscroll,
GtkPolicyType vscroll )
{
int width;
int height;
GtkRequisition req;
GdkScreen * screen;
int width, height;
screen = gtk_widget_get_screen( GTK_WIDGET( wind ) );
GdkScreen * screen = gtk_widget_get_screen( GTK_WIDGET( wind ) );
gtk_scrolled_window_set_policy( scroll, GTK_POLICY_NEVER,
GTK_POLICY_NEVER );
GTK_POLICY_NEVER );
gtk_widget_size_request( GTK_WIDGET( wind ), &req );
req.height = MAX( req.height, 300 );
height = MIN( req.height, gdk_screen_get_height( screen ) / 5 * 4 );
gtk_scrolled_window_set_policy( scroll, GTK_POLICY_NEVER, vscroll );
gtk_widget_size_request( GTK_WIDGET( wind ), &req );
req.width = MAX( req.width, 500 );
width = MIN( req.width, gdk_screen_get_width( screen ) / 2 );
gtk_window_set_default_size( wind, width, height );
gtk_scrolled_window_set_policy( scroll, hscroll, vscroll );
}
struct action *
action_new( int id, int flags, const char * label, const char * stock )
{
struct action * act;
act = g_new0( struct action, 1 );
act->id = id;
act->flags = flags;
act->label = g_strdup( label );
act->stock = g_strdup( stock );
act->tool = NULL;
act->menu = NULL;
return act;
}
void
action_free( struct action * act )
{
g_free( act->label );
g_free( act->stock );
g_free( act );
}
GtkWidget *
action_maketool( struct action * act, const char * key,
GCallback func, gpointer data )
{
GtkToolItem * item;
item = gtk_tool_button_new_from_stock( act->stock );
if( NULL != act->label )
{
gtk_tool_button_set_label( GTK_TOOL_BUTTON( item ), act->label );
}
g_object_set_data( G_OBJECT( item ), key, act );
g_signal_connect( item, "clicked", func, data );
gtk_widget_show( GTK_WIDGET( item ) );
return GTK_WIDGET( item );
}
GtkWidget *
action_makemenu( struct action * act, const char * actkey,
GtkAccelGroup * accel, const char * path, guint keyval,
GCallback func, gpointer data )
{
GtkWidget * item, * label;
GdkModifierType mod;
GtkStockItem stock;
const char * name;
char * joined;
mod = GDK_CONTROL_MASK;
name = act->label;
if( NULL == act->stock )
{
item = gtk_menu_item_new_with_label( act->label );
}
else
{
item = gtk_image_menu_item_new_from_stock( act->stock, NULL );
if( NULL == act->label )
{
if( gtk_stock_lookup( act->stock, &stock ) )
{
name = stock.label;
if( 0 == keyval )
{
keyval = stock.keyval;
mod = stock.modifier;
}
}
}
else
{
label = gtk_bin_get_child( GTK_BIN( item ) );
gtk_label_set_text( GTK_LABEL( label ), act->label );
}
}
if( NULL != accel && 0 < keyval && NULL != name )
{
joined = g_strjoin( "/", path, name, NULL );
gtk_accel_map_add_entry( joined, keyval, mod );
gtk_widget_set_accel_path( item, joined, accel );
g_free( joined );
}
g_object_set_data( G_OBJECT( item ), actkey, act );
g_signal_connect( item, "activate", func, data );
gtk_widget_show( item );
return item;
}
void
errmsg( GtkWindow * wind, const char * format, ... )
{
@ -519,3 +429,39 @@ errcb(GtkWidget *widget, int resp SHUTUP, gpointer data) {
gtk_widget_destroy(widget);
}
/* pop up the context menu if a user right-clicks.
if the row they right-click on isn't selected, select it. */
gboolean
on_tree_view_button_pressed (GtkWidget * view,
GdkEventButton * event,
gpointer func)
{
GtkTreeView * tv = GTK_TREE_VIEW( view );
if (event->type == GDK_BUTTON_PRESS && event->button == 3)
{
GtkTreeSelection * selection = gtk_tree_view_get_selection(tv);
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos (tv,
(gint) event->x,
(gint) event->y,
&path, NULL, NULL, NULL))
{
if (!gtk_tree_selection_path_is_selected (selection, path))
{
gtk_tree_selection_unselect_all (selection);
gtk_tree_selection_select_path (selection, path);
}
gtk_tree_path_free(path);
}
typedef void (PopupFunc)(GtkWidget*, GdkEventButton*);
((PopupFunc*)func)(view, event);
return TRUE;
}
return FALSE;
}

View File

@ -46,24 +46,6 @@ enum tr_torrent_action { TR_TOR_LEAVE, TR_TOR_COPY, TR_TOR_MOVE };
/* used for a callback function with a data parameter */
typedef void (*callbackfunc_t)(void*);
/* flags indicating where and when an action is valid */
#define ACTF_TOOL ( 1 << 0 ) /* appear in the toolbar */
#define ACTF_MENU ( 1 << 1 ) /* appear in the popup menu */
#define ACTF_ALWAYS ( 1 << 2 ) /* available regardless of selection */
#define ACTF_ACTIVE ( 1 << 3 ) /* available for active torrent */
#define ACTF_INACTIVE ( 1 << 4 ) /* available for inactive torrent */
#define ACTF_SEPARATOR ( 1 << 5 ) /* dummy action to create menu separator */
/* appear in the toolbar and the popup menu */
#define ACTF_WHEREVER ( ACTF_TOOL | ACTF_MENU )
/* available if there is something selected */
#define ACTF_WHATEVER ( ACTF_ACTIVE | ACTF_INACTIVE )
/* checking action flags against torrent status */
#define ACT_ISAVAIL( flags, status ) \
( ( ACTF_ACTIVE & (flags) && TR_STATUS_ACTIVE & (status) ) || \
( ACTF_INACTIVE & (flags) && TR_STATUS_INACTIVE & (status) ) || \
ACTF_ALWAYS & (flags) )
/* try to interpret a string as a textual representation of a boolean */
/* note that this isn't localized */
gboolean
@ -131,30 +113,6 @@ getdownloaddir( void );
#ifdef GTK_MAJOR_VERSION
/* action handling */
struct action
{
int id;
int flags;
char * label;
char * stock;
GtkWidget * tool;
GtkWidget * menu;
callbackfunc_t func;
gpointer data;
};
struct action *
action_new( int id, int flags, const char * label, const char * stock );
void
action_free( struct action * act );
GtkWidget *
action_maketool( struct action * act, const char * key,
GCallback func, gpointer data );
GtkWidget *
action_makemenu( struct action * act, const char * actkey,
GtkAccelGroup * accel, const char * path, guint keyval,
GCallback func, gpointer data );
/* here there be dragons */
void
sizingmagic( GtkWindow * wind, GtkScrolledWindow * scroll,
@ -184,6 +142,13 @@ GtkWidget *
verrmsg_full( GtkWindow * wind, callbackfunc_t func, void * data,
const char * format, va_list ap );
/* pop up the context menu if a user right-clicks.
if the row they right-click on isn't selected, select it. */
gboolean
on_tree_view_button_pressed (GtkWidget * view,
GdkEventButton * event,
gpointer unused);
#endif /* GTK_MAJOR_VERSION */
#endif /* TG_UTIL_H */

View File

@ -29,19 +29,8 @@
# define lrintf(a) ((int)(0.5+(a)))
#endif
/* We may try to allocate and free tables of size 0. Quick and dirty
way to handle it... */
void * tr_malloc( size_t size )
{
if( !size )
return NULL;
return malloc( size );
}
void tr_free( void * p )
{
if( p )
free( p );
}
/* We may try to allocate and free tables of size 0.
Quick and dirty way to handle it... */
#define malloc tr_malloc
#define free tr_free

View File

@ -23,6 +23,29 @@
*****************************************************************************/
#include "transmission.h"
#include "completion.h"
struct tr_completion_s
{
tr_torrent_t * tor;
tr_bitfield_t * blockBitfield;
uint8_t * blockDownloaders;
tr_bitfield_t * pieceBitfield;
/* a block is missing if we don't have it AND there's not a request pending */
int * missingBlocks;
/* a block is complete if and only if we have it */
int * completeBlocks;
/* rather than calculating these over and over again in loops,
just calculate them once */
int nBlocksInPiece;
int nBlocksInLastPiece;
};
#define tr_cpCountBlocks(cp,piece) \
(piece==cp->tor->info.pieceCount-1 ? cp->nBlocksInLastPiece : cp->nBlocksInPiece)
tr_completion_t * tr_cpInit( tr_torrent_t * tor )
{
@ -34,6 +57,11 @@ tr_completion_t * tr_cpInit( tr_torrent_t * tor )
cp->blockDownloaders = malloc( tor->blockCount );
cp->pieceBitfield = tr_bitfieldNew( tor->info.pieceCount );
cp->missingBlocks = malloc( tor->info.pieceCount * sizeof( int ) );
cp->completeBlocks = malloc( tor->info.pieceCount * sizeof( int ) );
cp->nBlocksInLastPiece = tr_pieceCountBlocks ( tor->info.pieceCount - 1 );
cp->nBlocksInPiece = tor->info.pieceCount==1 ? cp->nBlocksInLastPiece
: tr_pieceCountBlocks( 0 );
tr_cpReset( cp );
@ -46,6 +74,7 @@ void tr_cpClose( tr_completion_t * cp )
free( cp->blockDownloaders );
tr_bitfieldFree( cp->pieceBitfield );
free( cp->missingBlocks );
free( cp->completeBlocks );
free( cp );
}
@ -54,89 +83,67 @@ void tr_cpReset( tr_completion_t * cp )
tr_torrent_t * tor = cp->tor;
int i;
cp->blockCount = 0;
tr_bitfieldClear( cp->blockBitfield );
memset( cp->blockDownloaders, 0, tor->blockCount );
tr_bitfieldClear( cp->pieceBitfield );
for( i = 0; i < tor->info.pieceCount; i++ )
{
cp->missingBlocks[i] = tr_pieceCountBlocks( i );
for( i = 0; i < tor->info.pieceCount; ++i ) {
cp->missingBlocks[i] = tr_cpCountBlocks( cp, i );
cp->completeBlocks[i] = 0;
}
}
float tr_cpCompletionAsFloat( tr_completion_t * cp )
int tr_cpPieceHasAllBlocks( const tr_completion_t * cp, int piece )
{
return (float) cp->blockCount / (float) cp->tor->blockCount;
return tr_cpPieceIsComplete( cp, piece );
}
uint64_t tr_cpLeftBytes( tr_completion_t * cp )
int tr_cpPieceIsComplete( const tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
uint64_t left;
left = (uint64_t) ( cp->tor->blockCount - cp->blockCount ) *
(uint64_t) tor->blockSize;
if( !tr_bitfieldHas( cp->blockBitfield, cp->tor->blockCount - 1 ) &&
tor->info.totalSize % tor->blockSize )
{
left += tor->info.totalSize % tor->blockSize;
left -= tor->blockSize;
}
return left;
const int total = tr_cpCountBlocks( cp, piece );
const int have = cp->completeBlocks[piece];
assert( have <= total );
return have == total;
}
/* Pieces */
int tr_cpPieceHasAllBlocks( tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int startBlock = tr_pieceStartBlock( piece );
int endBlock = startBlock + tr_pieceCountBlocks( piece );
int i;
for( i = startBlock; i < endBlock; i++ )
{
if( !tr_bitfieldHas( cp->blockBitfield, i ) )
{
return 0;
}
}
return 1;
}
int tr_cpPieceIsComplete( tr_completion_t * cp, int piece )
{
return tr_bitfieldHas( cp->pieceBitfield, piece );
}
tr_bitfield_t * tr_cpPieceBitfield( tr_completion_t * cp )
const tr_bitfield_t * tr_cpPieceBitfield( const tr_completion_t * cp )
{
return cp->pieceBitfield;
}
void tr_cpPieceAdd( tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int startBlock, endBlock, i;
int i;
const tr_torrent_t * tor = cp->tor;
const int n_blocks = tr_cpCountBlocks( cp, piece );
const int startBlock = tr_pieceStartBlock( piece );
const int endBlock = startBlock + n_blocks;
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + tr_pieceCountBlocks( piece );
for( i = startBlock; i < endBlock; i++ )
{
tr_cpBlockAdd( cp, i );
}
cp->completeBlocks[piece] = n_blocks;
for( i=startBlock; i<endBlock; ++i )
if( !cp->blockDownloaders[i] )
--cp->missingBlocks[piece];
tr_bitfieldAddRange( cp->blockBitfield, startBlock, endBlock-1 );
tr_bitfieldAdd( cp->pieceBitfield, piece );
}
void tr_cpPieceRem( tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int startBlock, endBlock, i;
int i;
const tr_torrent_t * tor = cp->tor;
const int n_blocks = tr_cpCountBlocks( cp, piece );
const int startBlock = tr_pieceStartBlock( piece );
const int endBlock = startBlock + n_blocks;
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + tr_pieceCountBlocks( piece );
for( i = startBlock; i < endBlock; i++ )
{
tr_cpBlockRem( cp, i );
}
cp->completeBlocks[piece] = 0;
for( i=startBlock; i<endBlock; ++i )
if( !cp->blockDownloaders[i] )
++cp->missingBlocks[piece];
tr_bitfieldRemRange ( cp->blockBitfield, startBlock, endBlock-1 );
tr_bitfieldRem( cp->pieceBitfield, piece );
}
@ -154,7 +161,13 @@ void tr_cpDownloaderAdd( tr_completion_t * cp, int block )
void tr_cpDownloaderRem( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
tr_torrent_t * tor;
assert( cp != NULL );
assert( cp->tor != NULL );
assert( 0 <= block );
tor = cp->tor;
(cp->blockDownloaders[block])--;
if( !cp->blockDownloaders[block] && !tr_cpBlockIsComplete( cp, block ) )
{
@ -164,129 +177,90 @@ void tr_cpDownloaderRem( tr_completion_t * cp, int block )
int tr_cpBlockIsComplete( const tr_completion_t * cp, int block )
{
assert( cp != NULL );
assert( 0 <= block );
return tr_bitfieldHas( cp->blockBitfield, block );
}
void tr_cpBlockAdd( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
const tr_torrent_t * tor;
assert( cp != NULL );
assert( cp->tor != NULL );
assert( 0 <= block );
tor = cp->tor;
if( !tr_cpBlockIsComplete( cp, block ) )
{
(cp->blockCount)++;
const int piece = tr_blockPiece( block );
++cp->completeBlocks[piece];
if( cp->completeBlocks[piece] == tr_cpCountBlocks( cp, piece ) )
tr_bitfieldAdd( cp->pieceBitfield, piece );
if( !cp->blockDownloaders[block] )
{
(cp->missingBlocks[tr_blockPiece(block)])--;
}
cp->missingBlocks[piece]--;
tr_bitfieldAdd( cp->blockBitfield, block );
}
tr_bitfieldAdd( cp->blockBitfield, block );
}
void tr_cpBlockRem( tr_completion_t * cp, int block )
const tr_bitfield_t * tr_cpBlockBitfield( const tr_completion_t * cp )
{
tr_torrent_t * tor = cp->tor;
if( tr_cpBlockIsComplete( cp, block ) )
{
(cp->blockCount)--;
if( !cp->blockDownloaders[block] )
{
(cp->missingBlocks[tr_blockPiece(block)])++;
}
}
tr_bitfieldRem( cp->blockBitfield, block );
}
assert( cp != NULL );
tr_bitfield_t * tr_cpBlockBitfield( tr_completion_t * cp )
{
return cp->blockBitfield;
}
void tr_cpBlockBitfieldSet( tr_completion_t * cp, tr_bitfield_t * bitfield )
void
tr_cpBlockBitfieldSet( tr_completion_t * cp, tr_bitfield_t * bitfield )
{
tr_torrent_t * tor = cp->tor;
int i, j;
int startBlock, endBlock;
int pieceComplete;
int i;
for( i = 0; i < cp->tor->info.pieceCount; i++ )
{
startBlock = tr_pieceStartBlock( i );
endBlock = startBlock + tr_pieceCountBlocks( i );
pieceComplete = 1;
assert( cp != NULL );
assert( bitfield != NULL );
for( j = startBlock; j < endBlock; j++ )
{
if( tr_bitfieldHas( bitfield, j ) )
{
tr_cpBlockAdd( cp, j );
}
else
{
pieceComplete = 0;
}
}
if( pieceComplete )
{
tr_cpPieceAdd( cp, i );
}
}
tr_cpReset( cp );
for( i=0; i < cp->tor->blockCount; ++i )
if( tr_bitfieldHas( bitfield, i ) )
tr_cpBlockAdd( cp, i );
}
float tr_cpPercentBlocksInPiece( tr_completion_t * cp, int piece )
float tr_cpPercentBlocksInPiece( const tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int i;
int blockCount, startBlock, endBlock;
int complete;
tr_bitfield_t * bitfield;
blockCount = tr_pieceCountBlocks( piece );
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + blockCount;
complete = 0;
bitfield = cp->blockBitfield;
assert( cp != NULL );
for( i = startBlock; i < endBlock; i++ )
{
if( tr_bitfieldHas( bitfield, i ) )
{
complete++;
}
}
return (float)complete / (float)blockCount;
return cp->completeBlocks[piece] / (double)tr_cpCountBlocks( cp, piece );
}
int tr_cpMissingBlockInPiece( const tr_completion_t * cp, int piece )
{
int i;
const tr_torrent_t * tor = cp->tor;
int start, count, end, i;
const int start = tr_pieceStartBlock( piece );
const int end = start + tr_cpCountBlocks( cp, piece );
start = tr_pieceStartBlock( piece );
count = tr_pieceCountBlocks( piece );
end = start + count;
for( i = start; i < end; i++ )
{
if( tr_cpBlockIsComplete( cp, i ) || cp->blockDownloaders[i] )
{
continue;
}
return i;
}
for( i = start; i < end; ++i )
if( !tr_cpBlockIsComplete( cp, i ) && !cp->blockDownloaders[i] )
return i;
return -1;
}
int tr_cpMostMissingBlockInPiece( tr_completion_t * cp, int piece,
int * downloaders )
int tr_cpMostMissingBlockInPiece( const tr_completion_t * cp,
int piece,
int * downloaders )
{
tr_torrent_t * tor = cp->tor;
int start, count, end, i;
int * pool, poolSize, min, ret;
start = tr_pieceStartBlock( piece );
count = tr_pieceCountBlocks( piece );
count = tr_cpCountBlocks( cp, piece );
end = start + count;
pool = malloc( count * sizeof( int ) );
@ -324,3 +298,129 @@ int tr_cpMostMissingBlockInPiece( tr_completion_t * cp, int piece,
return ret;
}
int
tr_cpMissingBlocksForPiece( const tr_completion_t * cp, int piece )
{
assert( cp != NULL );
return cp->missingBlocks[piece];
}
/***
****
***/
cp_status_t
tr_cpGetStatus ( const tr_completion_t * cp )
{
int i;
int ret = TR_CP_COMPLETE;
const tr_info_t * info;
assert( cp != NULL );
assert( cp->tor != NULL );
info = &cp->tor->info;
for( i=0; i<info->pieceCount; ++i ) {
if( tr_cpPieceIsComplete( cp, i ) )
continue;
if( info->pieces[i].priority != TR_PRI_DND)
return TR_CP_INCOMPLETE;
ret = TR_CP_DONE;
}
return ret;
}
uint64_t
tr_cpLeftUntilComplete ( const tr_completion_t * cp )
{
int i;
uint64_t b=0;
const tr_torrent_t * tor;
const tr_info_t * info;
assert( cp != NULL );
assert( cp->tor != NULL );
tor = cp->tor;
info = &tor->info;
for( i=0; i<info->pieceCount; ++i )
if( !tr_cpPieceIsComplete( cp, i ) )
b += ( tr_cpCountBlocks( cp, i ) - cp->completeBlocks[ i ] );
return b * tor->blockSize;;
}
uint64_t
tr_cpLeftUntilDone ( const tr_completion_t * cp )
{
int i;
uint64_t b=0;
const tr_torrent_t * tor;
const tr_info_t * info;
assert( cp != NULL );
assert( cp->tor != NULL );
tor = cp->tor;
info = &tor->info;
for( i=0; i<info->pieceCount; ++i )
if( !tr_cpPieceIsComplete( cp, i ) && info->pieces[i].priority != TR_PRI_DND )
b += tor->blockSize * (tr_cpCountBlocks( cp, i ) - cp->completeBlocks[ i ] );
return b;
}
float
tr_cpPercentComplete ( const tr_completion_t * cp )
{
int i;
uint64_t have=0;
const tr_torrent_t * tor;
assert( cp != NULL );
assert( cp->tor != NULL );
tor = cp->tor;
for( i=0; i<tor->info.pieceCount; ++i )
have += cp->completeBlocks[ i ];
return tor->blockCount ? (float)have / (float)tor->blockCount : 0.0f;
}
float
tr_cpPercentDone( const tr_completion_t * cp )
{
int i;
uint64_t have=0, total=0;
const tr_torrent_t * tor;
assert( cp != NULL );
assert( cp->tor != NULL );
tor = cp->tor;
for( i=0; i<tor->info.pieceCount; ++i ) {
if( tor->info.pieces[i].priority != TR_PRI_DND) {
total += tr_cpCountBlocks( cp, i );
have += cp->completeBlocks[ i ];
}
}
return !total ? 0.0f : (float)have / (float)total;
}
uint64_t
tr_cpDownloadedValid( const tr_completion_t * cp )
{
int i, n;
uint64_t b=0;
for( i=0, n=cp->tor->info.pieceCount; i<n; ++i )
b += cp->completeBlocks[ i ];
return b * cp->tor->blockSize;
}

View File

@ -22,49 +22,44 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
struct tr_completion_s
{
tr_torrent_t * tor;
tr_bitfield_t * blockBitfield;
uint8_t * blockDownloaders;
int blockCount;
tr_bitfield_t * pieceBitfield;
int * missingBlocks;
};
#ifndef TR_COMPLETION_H
#define TR_COMPLETION_H
tr_completion_t * tr_cpInit( tr_torrent_t * );
void tr_cpClose( tr_completion_t * );
void tr_cpReset( tr_completion_t * );
#include "transmission.h"
tr_completion_t * tr_cpInit( tr_torrent_t * );
void tr_cpClose( tr_completion_t * );
void tr_cpReset( tr_completion_t * );
/* General */
float tr_cpCompletionAsFloat( tr_completion_t * );
static inline int tr_cpIsSeeding( tr_completion_t * cp )
{
return ( cp->blockCount == cp->tor->blockCount );
}
uint64_t tr_cpLeftBytes( tr_completion_t * );
cp_status_t tr_cpGetStatus ( const tr_completion_t * );
uint64_t tr_cpDownloadedValid( const tr_completion_t * );
uint64_t tr_cpLeftUntilComplete( const tr_completion_t * );
uint64_t tr_cpLeftUntilDone( const tr_completion_t * );
float tr_cpPercentComplete( const tr_completion_t * );
float tr_cpPercentDone( const tr_completion_t * );
/* Pieces */
int tr_cpPieceHasAllBlocks( tr_completion_t *, int piece );
int tr_cpPieceIsComplete( tr_completion_t *, int piece );
tr_bitfield_t * tr_cpPieceBitfield( tr_completion_t * );
void tr_cpPieceAdd( tr_completion_t *, int piece );
void tr_cpPieceRem( tr_completion_t *, int piece );
int tr_cpPieceHasAllBlocks( const tr_completion_t *, int piece );
int tr_cpPieceIsComplete( const tr_completion_t *, int piece );
const tr_bitfield_t * tr_cpPieceBitfield( const tr_completion_t* );
void tr_cpPieceAdd( tr_completion_t *, int piece );
void tr_cpPieceRem( tr_completion_t *, int piece );
/* Blocks */
void tr_cpDownloaderAdd( tr_completion_t *, int block );
void tr_cpDownloaderRem( tr_completion_t *, int block );
int tr_cpBlockIsComplete( const tr_completion_t *, int block );
void tr_cpBlockAdd( tr_completion_t *, int block );
void tr_cpBlockRem( tr_completion_t *, int block );
tr_bitfield_t * tr_cpBlockBitfield( tr_completion_t * );
void tr_cpBlockBitfieldSet( tr_completion_t *, tr_bitfield_t * );
float tr_cpPercentBlocksInPiece( tr_completion_t * cp, int piece );
void tr_cpDownloaderAdd( tr_completion_t *, int block );
void tr_cpDownloaderRem( tr_completion_t *, int block );
int tr_cpBlockIsComplete( const tr_completion_t *, int block );
void tr_cpBlockAdd( tr_completion_t *, int block );
const tr_bitfield_t * tr_cpBlockBitfield( const tr_completion_t * );
void tr_cpBlockBitfieldSet( tr_completion_t *, tr_bitfield_t * );
float tr_cpPercentBlocksInPiece( const tr_completion_t * cp, int piece );
/* Missing = we don't have it and we are not getting it from any peer yet */
static inline int tr_cpMissingBlocksForPiece( const tr_completion_t * cp, int piece )
{
return cp->missingBlocks[piece];
}
int tr_cpMissingBlockInPiece( const tr_completion_t *, int piece );
int tr_cpMostMissingBlockInPiece( tr_completion_t *, int piece,
int * downloaders );
int tr_cpMissingBlocksForPiece( const tr_completion_t * cp, int piece );
int tr_cpMissingBlockInPiece( const tr_completion_t *, int piece );
int tr_cpMostMissingBlockInPiece( const tr_completion_t *, int piece,
int * downloaders );
#endif

View File

@ -0,0 +1,415 @@
/******************************************************************************
* $Id:$
*
* Copyright (c) 2005-2007 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.
*****************************************************************************/
/***********************************************************************
* Fast resume
***********************************************************************
* The format of the resume file is a 4 byte format version (currently 1),
* followed by several variable-sized blocks of data. Each block is
* preceded by a 1 byte ID and a 4 byte length. The currently recognized
* IDs are defined below by the FR_ID_* macros. The length does not include
* the 5 bytes for the ID and length.
*
* The name of the resume file is "resume.<hash>-<tag>", although
* older files with a name of "resume.<hash>" will be recognized if
* the former doesn't exist.
*
* All values are stored in the native endianness. Moving a
* libtransmission resume file from an architecture to another will not
* work, although it will not hurt either (the version will be wrong,
* so the resume file will not be read).
**********************************************************************/
#include "transmission.h"
#include "fastresume.h"
/* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */
typedef uint64_t tr_time_t;
/* deprecated */
#define FR_ID_PROGRESS_SLOTS 0x01
/* number of bytes downloaded */
#define FR_ID_DOWNLOADED 0x02
/* number of bytes uploaded */
#define FR_ID_UPLOADED 0x03
/* IPs and ports of connectable peers */
#define FR_ID_PEERS 0x04
/* progress data:
* - 4 bytes * number of files: mtimes of files
* - 1 bit * number of blocks: whether we have the block or not
*/
#define FR_ID_PROGRESS 0x05
/* macros for the length of various pieces of the progress data */
#define FR_MTIME_LEN( t ) \
( sizeof(tr_time_t) * (t)->info.fileCount )
#define FR_BLOCK_BITFIELD_LEN( t ) \
( ( (t)->blockCount + 7 ) / 8 )
#define FR_PROGRESS_LEN( t ) \
( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
static void
fastResumeFileName( char * path, size_t size, const tr_torrent_t * tor, int tag )
{
if( tag )
{
snprintf( path, size, "%s/resume.%s-%s", tr_getCacheDirectory(),
tor->info.hashString, tor->handle->tag );
}
else
{
snprintf( path, size, "%s/resume.%s", tr_getCacheDirectory(),
tor->info.hashString );
}
}
static tr_time_t*
getMTimes( const tr_torrent_t * tor, int * setme_n )
{
int i;
const int n = tor->info.fileCount;
tr_time_t * m = calloc( n, sizeof(tr_time_t) );
for( i=0; i<n; ++i ) {
char fname[MAX_PATH_LENGTH];
struct stat sb;
snprintf( fname, sizeof(fname), "%s/%s",
tor->destination, tor->info.files[i].name );
if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
#ifdef SYS_DARWIN
m[i] = sb.st_mtimespec.tv_sec;
#else
m[i] = sb.st_mtime;
#endif
}
}
*setme_n = n;
return m;
}
static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
uint32_t count, FILE * file )
{
uint32_t datalen = size * count;
fwrite( &id, 1, 1, file );
fwrite( &datalen, 4, 1, file );
fwrite( data, size, count, file );
}
void fastResumeSave( const tr_torrent_t * tor )
{
char path[MAX_PATH_LENGTH];
FILE * file;
const int version = 1;
uint64_t total;
fastResumeFileName( path, sizeof path, tor, 1 );
file = fopen( path, "w" );
if( NULL == file ) {
tr_err( "Couldn't open '%s' for writing", path );
return;
}
/* Write format version */
fwrite( &version, 4, 1, file );
/* Write progress data */
if (1) {
int n;
tr_time_t * mtimes;
uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
uint8_t * walk = buf;
const tr_bitfield_t * bitfield;
/* mtimes */
mtimes = getMTimes( tor, &n );
memcpy( walk, mtimes, n*sizeof(tr_time_t) );
walk += n * sizeof(tr_time_t);
/* completion bitfield */
bitfield = tr_cpBlockBitfield( tor->completion );
assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
memcpy( walk, bitfield->bits, bitfield->len );
walk += bitfield->len;
/* write it */
assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
/* cleanup */
free( mtimes );
free( buf );
}
/* Write download and upload totals */
total = tor->downloadedCur + tor->downloadedPrev;
fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
total = tor->uploadedCur + tor->uploadedPrev;
fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
{
/* Write IPs and ports of connectable peers, if any */
int size;
uint8_t * buf = NULL;
if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
{
fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
free( buf );
}
}
fclose( file );
tr_dbg( "Resume file '%s' written", path );
}
static int
fastResumeLoadProgress( const tr_torrent_t * tor,
tr_bitfield_t * uncheckedPieces,
FILE * file )
{
const size_t len = FR_PROGRESS_LEN( tor );
uint8_t * buf = calloc( len, 1 );
uint8_t * walk = buf;
if( len != fread( buf, 1, len, file ) ) {
tr_inf( "Couldn't read from resume file" );
free( buf );
return TR_ERROR_IO_OTHER;
}
/* compare file mtimes */
if (1) {
int i, n;
tr_time_t * curMTimes = getMTimes( tor, &n );
const tr_time_t * oldMTimes = (const tr_time_t *) walk;
for( i=0; i<n; ++i ) {
if ( !curMTimes[i] || ( curMTimes[i]!=oldMTimes[i] ) ) {
const tr_file_t * file = &tor->info.files[i];
tr_inf( "File '%s' mtimes differ-- flaggin pieces [%d..%d]",
file->name, file->firstPiece, file->lastPiece);
tr_bitfieldAddRange( uncheckedPieces,
file->firstPiece, file->lastPiece );
}
}
free( curMTimes );
walk += n * sizeof(tr_time_t);
}
/* get the completion bitfield */
if (1) {
tr_bitfield_t bitfield;
memset( &bitfield, 0, sizeof bitfield );
bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
bitfield.bits = walk;
tr_cpBlockBitfieldSet( tor->completion, &bitfield );
}
free( buf );
return TR_OK;
}
static int
fastResumeLoadOld( tr_torrent_t * tor,
tr_bitfield_t * uncheckedPieces,
FILE * file )
{
/* Check the size */
const int size = 4 + FR_PROGRESS_LEN( tor );
fseek( file, 0, SEEK_END );
if( ftell( file ) != size )
{
tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
(int)ftell( file ), size );
fclose( file );
return 1;
}
/* load progress information */
fseek( file, 4, SEEK_SET );
if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
{
fclose( file );
return 1;
}
fclose( file );
tr_inf( "Fast resuming successful (version 0)" );
return 0;
}
int
fastResumeLoad( tr_torrent_t * tor,
tr_bitfield_t * uncheckedPieces )
{
char path[MAX_PATH_LENGTH];
FILE * file;
int version = 0;
uint8_t id;
uint32_t len;
int ret;
assert( tor != NULL );
assert( uncheckedPieces != NULL );
/* Open resume file */
fastResumeFileName( path, sizeof path, tor, 1 );
file = fopen( path, "r" );
if( NULL == file )
{
if( ENOENT == errno )
{
fastResumeFileName( path, sizeof path, tor, 0 );
file = fopen( path, "r" );
if( NULL != file )
{
goto good;
}
fastResumeFileName( path, sizeof path, tor, 1 );
}
tr_inf( "Could not open '%s' for reading", path );
return 1;
}
good:
tr_dbg( "Resume file '%s' loaded", path );
/* Check format version */
fread( &version, 4, 1, file );
if( 0 == version )
{
return fastResumeLoadOld( tor, uncheckedPieces, file );
}
if( 1 != version )
{
tr_inf( "Resume file has version %d, not supported", version );
fclose( file );
return 1;
}
ret = 1;
/* read each block of data */
while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
{
switch( id )
{
case FR_ID_PROGRESS:
/* read progress data */
if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
{
if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
{
if( feof( file ) || ferror( file ) )
{
fclose( file );
return 1;
}
}
else
{
ret = 0;
}
continue;
}
break;
case FR_ID_DOWNLOADED:
/* read download total */
if( 8 == len)
{
if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
{
fclose( file );
return 1;
}
continue;
}
break;
case FR_ID_UPLOADED:
/* read upload total */
if( 8 == len)
{
if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
{
fclose( file );
return 1;
}
continue;
}
break;
case FR_ID_PEERS:
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
{
int used;
uint8_t * buf = malloc( len );
if( 1 != fread( buf, len, 1, file ) )
{
free( buf );
fclose( file );
return 1;
}
used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
buf, len / 6 );
tr_dbg( "found %i peers in resume file, used %i",
len / 6, used );
free( buf );
}
continue;
default:
break;
}
/* if we didn't read the data, seek past it */
tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
fseek( file, len, SEEK_CUR );
}
fclose( file );
if( !ret )
{
tr_inf( "Fast resuming successful" );
}
return ret;
}
void
fastResumeRemove( tr_torrent_t * tor )
{
char file[MAX_PATH_LENGTH];
fastResumeFileName( file, sizeof file, tor, NULL != tor->handle->tag );
if ( unlink( file ) )
{
tr_inf( "Removing fast resume file failed" );
}
}

View File

@ -22,421 +22,14 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
/***********************************************************************
* Fast resume
***********************************************************************
* The format of the resume file is a 4 byte format version (currently 1),
* followed by several variable-sized blocks of data. Each block is
* preceded by a 1 byte ID and a 4 byte length. The currently recognized
* IDs are defined below by the FR_ID_* macros. The length does not include
* the 5 bytes for the ID and length.
*
* The name of the resume file is "resume.<hash>-<tag>", although
* older files with a name of "resume.<hash>" will be recognized if
* the former doesn't exist.
*
* All values are stored in the native endianness. Moving a
* libtransmission resume file from an architecture to another will not
* work, although it will not hurt either (the version will be wrong,
* so the resume file will not be read).
**********************************************************************/
#ifndef TR_FAST_RESUME_H
#define TR_FAST_RESUME_H
/* progress data:
* - 4 bytes * number of files: mtimes of files
* - 1 bit * number of blocks: whether we have the block or not
* - 4 bytes * number of pieces (byte aligned): the pieces that have
* been completed or started in each slot
*/
#define FR_ID_PROGRESS 0x01
/* number of bytes downloaded */
#define FR_ID_DOWNLOADED 0x02
/* number of bytes uploaded */
#define FR_ID_UPLOADED 0x03
/* IPs and ports of connectable peers */
#define FR_ID_PEERS 0x04
void fastResumeSave( const tr_torrent_t * tor );
/* macros for the length of various pieces of the progress data */
#define FR_MTIME_LEN( t ) \
( 4 * (t)->info.fileCount )
#define FR_BLOCK_BITFIELD_LEN( t ) \
( ( (t)->blockCount + 7 ) / 8 )
#define FR_SLOTPIECE_LEN( t ) \
( 4 * (t)->info.pieceCount )
#define FR_PROGRESS_LEN( t ) \
( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) + FR_SLOTPIECE_LEN( t ) )
int fastResumeLoad( tr_torrent_t * tor,
tr_bitfield_t * uncheckedPieces );
static void
fastResumeFileName( char * path, size_t size, tr_torrent_t * tor, int tag )
{
if( tag )
{
snprintf( path, size, "%s/resume.%s-%s", tr_getCacheDirectory(),
tor->info.hashString, tor->handle->tag );
}
else
{
snprintf( path, size, "%s/resume.%s", tr_getCacheDirectory(),
tor->info.hashString );
}
}
void fastResumeRemove( tr_torrent_t * tor );
static int fastResumeMTimes( tr_io_t * io, int * tab )
{
tr_torrent_t * tor = io->tor;
tr_info_t * inf = &tor->info;
int i;
char * path;
struct stat sb;
for( i = 0; i < inf->fileCount; i++ )
{
asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
if( stat( path, &sb ) )
{
tab[i] = 0xFFFFFFFF;
}
else if( S_ISREG( sb.st_mode ) )
{
#ifdef SYS_DARWIN
tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF );
#else
tab[i] = ( sb.st_mtime & 0x7FFFFFFF );
#endif
}
else
{
/* Empty folder */
tab[i] = 0;
}
free( path );
}
return 0;
}
static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
uint32_t count, FILE * file )
{
uint32_t datalen = size * count;
fwrite( &id, 1, 1, file );
fwrite( &datalen, 4, 1, file );
fwrite( data, size, count, file );
}
static void fastResumeSave( tr_io_t * io )
{
tr_torrent_t * tor = io->tor;
char path[MAX_PATH_LENGTH];
FILE * file;
int version = 1;
uint8_t * buf;
uint64_t total;
int size;
tr_bitfield_t * bitfield;
buf = malloc( FR_PROGRESS_LEN( tor ) );
/* Get file sizes */
if( fastResumeMTimes( io, (int*)buf ) )
{
free( buf );
return;
}
/* Create/overwrite the resume file */
fastResumeFileName( path, sizeof path, tor, 1 );
file = fopen( path, "w" );
if( NULL == file )
{
tr_err( "Could not open '%s' for writing", path );
free( buf );
return;
}
/* Write format version */
fwrite( &version, 4, 1, file );
/* Build and copy the bitfield for blocks */
bitfield = tr_cpBlockBitfield( tor->completion );
assert( FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
memcpy(buf + FR_MTIME_LEN( tor ), bitfield->bits, bitfield->len );
/* Copy the 'slotPiece' table */
memcpy(buf + FR_MTIME_LEN( tor ) + FR_BLOCK_BITFIELD_LEN( tor ),
io->slotPiece, FR_SLOTPIECE_LEN( tor ) );
/* Write progress data */
fastResumeWriteData( FR_ID_PROGRESS, buf, 1, FR_PROGRESS_LEN( tor ), file );
free( buf );
/* Write download and upload totals */
total = tor->downloadedCur + tor->downloadedPrev;
fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
total = tor->uploadedCur + tor->uploadedPrev;
fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
{
/* Write IPs and ports of connectable peers, if any */
if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
{
fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
free( buf );
}
}
fclose( file );
tr_dbg( "Resume file '%s' written", path );
}
static int fastResumeLoadProgress( tr_io_t * io, FILE * file )
{
tr_torrent_t * tor = io->tor;
tr_info_t * inf = &tor->info;
int * fileMTimes;
int i, j;
uint8_t * buf;
size_t len;
tr_bitfield_t bitfield;
len = FR_PROGRESS_LEN( tor );
buf = calloc( len, 1 );
if( len != fread( buf, 1, len, file ) )
{
tr_inf( "Could not read from resume file" );
free( buf );
return 1;
}
/* Compare file mtimes */
fileMTimes = malloc( FR_MTIME_LEN( tor ) );
if( fastResumeMTimes( io, fileMTimes ) )
{
free( buf );
free( fileMTimes );
return 1;
}
if( memcmp( fileMTimes, buf, FR_MTIME_LEN( tor ) ) )
{
tr_inf( "File mtimes don't match" );
free( buf );
free( fileMTimes );
return 1;
}
free( fileMTimes );
/* Copy the bitfield for blocks and fill blockHave */
memset( &bitfield, 0, sizeof bitfield );
bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
bitfield.bits = buf + FR_MTIME_LEN( tor );
tr_cpBlockBitfieldSet( tor->completion, &bitfield );
/* Copy the 'slotPiece' table */
memcpy( io->slotPiece, buf + FR_MTIME_LEN( tor ) +
FR_BLOCK_BITFIELD_LEN( tor ), FR_SLOTPIECE_LEN( tor ) );
free( buf );
/* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */
io->slotsUsed = 0;
for( i = 0; i < inf->pieceCount; i++ )
{
io->pieceSlot[i] = -1;
for( j = 0; j < inf->pieceCount; j++ )
{
if( io->slotPiece[j] == i )
{
// tr_dbg( "Has piece %d in slot %d", i, j );
io->pieceSlot[i] = j;
io->slotsUsed = MAX( io->slotsUsed, j + 1 );
break;
}
}
}
// tr_dbg( "Slot used: %d", io->slotsUsed );
return 0;
}
static int fastResumeLoadOld( tr_io_t * io, FILE * file )
{
tr_torrent_t * tor = io->tor;
int size;
/* Check the size */
size = 4 + FR_PROGRESS_LEN( tor );
fseek( file, 0, SEEK_END );
if( ftell( file ) != size )
{
tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
(int)ftell( file ), size );
fclose( file );
return 1;
}
/* load progress information */
fseek( file, 4, SEEK_SET );
if( fastResumeLoadProgress( io, file ) )
{
fclose( file );
return 1;
}
fclose( file );
tr_inf( "Fast resuming successful (version 0)" );
return 0;
}
static int fastResumeLoad( tr_io_t * io )
{
tr_torrent_t * tor = io->tor;
char path[MAX_PATH_LENGTH];
FILE * file;
int version = 0;
uint8_t id;
uint32_t len;
int ret;
/* Open resume file */
fastResumeFileName( path, sizeof path, tor, 1 );
file = fopen( path, "r" );
if( NULL == file )
{
if( ENOENT == errno )
{
fastResumeFileName( path, sizeof path, tor, 0 );
file = fopen( path, "r" );
if( NULL != file )
{
goto good;
}
fastResumeFileName( path, sizeof path, tor, 1 );
}
tr_inf( "Could not open '%s' for reading", path );
return 1;
}
good:
tr_dbg( "Resume file '%s' loaded", path );
/* Check format version */
fread( &version, 4, 1, file );
if( 0 == version )
{
return fastResumeLoadOld( io, file );
}
if( 1 != version )
{
tr_inf( "Resume file has version %d, not supported", version );
fclose( file );
return 1;
}
ret = 1;
/* read each block of data */
while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
{
switch( id )
{
case FR_ID_PROGRESS:
/* read progress data */
if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
{
if( fastResumeLoadProgress( io, file ) )
{
if( feof( file ) || ferror( file ) )
{
fclose( file );
return 1;
}
}
else
{
ret = 0;
}
continue;
}
break;
case FR_ID_DOWNLOADED:
/* read download total */
if( 8 == len)
{
if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
{
fclose( file );
return 1;
}
continue;
}
break;
case FR_ID_UPLOADED:
/* read upload total */
if( 8 == len)
{
if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
{
fclose( file );
return 1;
}
continue;
}
break;
case FR_ID_PEERS:
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
{
int used;
uint8_t * buf = malloc( len );
if( 1 != fread( buf, len, 1, file ) )
{
free( buf );
fclose( file );
return 1;
}
used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
buf, len / 6 );
tr_dbg( "found %i peers in resume file, used %i",
len / 6, used );
free( buf );
}
continue;
default:
break;
}
/* if we didn't read the data, seek past it */
tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
fseek( file, len, SEEK_CUR );
}
fclose( file );
if( !ret )
{
tr_inf( "Fast resuming successful" );
}
return ret;
}
static void fastResumeRemove( tr_torrent_t * tor )
{
char file[MAX_PATH_LENGTH];
fastResumeFileName( file, sizeof file, tor, NULL != tor->handle->tag );
if ( unlink( file ) )
{
tr_inf( "Removing fast resume file failed" );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -150,6 +150,13 @@ typedef enum { TR_NET_OK, TR_NET_ERROR, TR_NET_WAIT } tr_tristate_t;
#include "http.h"
#include "xml.h"
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
int tr_torrentAddCompact( tr_torrent_t * tor, int from,
uint8_t * buf, int count );
int tr_torrentAttachPeer( tr_torrent_t * tor, tr_peer_t * peer );
@ -168,7 +175,7 @@ struct tr_torrent_s
int status;
int error;
char errorString[128];
int finished;
int hasChangedState;
char * id;
char * key;

View File

@ -705,7 +705,7 @@ ipc_addinfo( benc_val_t * list, int tor, tr_info_t * inf, int types )
}
int
ipc_addstat( benc_val_t * list, int tor, tr_info_t * inf,
ipc_addstat( benc_val_t * list, int tor,
tr_stat_t * st, int types )
{
benc_val_t * dict, * item;
@ -746,7 +746,7 @@ ipc_addstat( benc_val_t * list, int tor, tr_info_t * inf,
switch( 1 << ii )
{
case IPC_ST_COMPLETED:
tr_bencInitInt( item, st->progress * ( float )inf->totalSize );
tr_bencInitInt( item, st->downloadedValid );
break;
case IPC_ST_DOWNSPEED:
tr_bencInitInt( item, st->rateDownload * 1024 );

View File

@ -155,7 +155,7 @@ uint8_t * ipc_mkvers ( size_t * );
uint8_t * ipc_mkgetinfo( struct ipc_info *, size_t *, enum ipc_msg, int64_t,
int, const int * );
int ipc_addinfo ( benc_val_t *, int, tr_info_t *, int );
int ipc_addstat ( benc_val_t *, int, tr_info_t *, tr_stat_t *, int );
int ipc_addstat ( benc_val_t *, int, tr_stat_t *, int );
/* sets errno to EINVAL on parse error or
EPERM for unsupported protocol version */

498
libtransmission/makemeta.c Normal file
View File

@ -0,0 +1,498 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <dirent.h>
#include <stdio.h> /* FILE, snprintf, stderr */
#include <stdlib.h> /* malloc, calloc */
#include "transmission.h"
#include "internal.h" /* for tr_torrent_t */
#include "bencode.h"
#include "makemeta.h"
#include "platform.h" /* threads, locks */
#include "shared.h" /* shared lock */
#include "version.h"
/****
*****
****/
struct FileList
{
struct FileList * next;
char filename[MAX_PATH_LENGTH];
};
static struct FileList*
getFiles( const char * dir,
const char * base,
struct FileList * list )
{
int i;
char buf[MAX_PATH_LENGTH];
struct stat sb;
DIR * odir = NULL;
sb.st_size = 0;
snprintf( buf, sizeof(buf), "%s"TR_PATH_DELIMITER_STR"%s", dir, base );
i = stat( buf, &sb );
if( i ) {
tr_err("makemeta couldn't stat \"%s\"; skipping. (%s)", buf, strerror(errno));
return list;
}
if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
{
struct dirent *d;
for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
if( strcmp( d->d_name,"." ) && strcmp( d->d_name,".." ) )
list = getFiles( buf, d->d_name, list );
closedir( odir );
}
else if( S_ISREG( sb.st_mode ) )
{
struct FileList * node = malloc( sizeof( struct FileList ) );
snprintf( node->filename, sizeof( node->filename ), "%s", buf );
node->next = list;
list = node;
}
return list;
}
static void
freeFileList( struct FileList * list )
{
while( list ) {
struct FileList * tmp = list->next;
free( list );
list = tmp;
}
}
static off_t
getFileSize ( const char * filename )
{
struct stat sb;
sb.st_size = 0;
stat( filename, &sb );
return sb.st_size;
}
static int
bestPieceSize( uint64_t totalSize )
{
const int MiB = 1048576;
const int GiB = totalSize / (uint64_t)1073741824;
/* almost always best to have a piee size of 512 or 256 kb.
common practice seems to be to bump up to 1MB pieces at
at total size of around 8GiB or so */
if( GiB >= 8 )
return MiB;
if( GiB >= 1 )
return MiB / 2;
return MiB / 4;
}
/****
*****
****/
static int pstrcmp( const void * va, const void * vb)
{
const char * a = *(const char**) va;
const char * b = *(const char**) vb;
return strcmp( a, b );
}
tr_metainfo_builder_t*
tr_metaInfoBuilderCreate( tr_handle_t * handle, const char * topFile )
{
int i;
struct FileList * files;
const struct FileList * walk;
tr_metainfo_builder_t * ret = calloc( 1, sizeof(tr_metainfo_builder_t) );
ret->top = tr_strdup( topFile );
ret->handle = handle;
if (1) {
struct stat sb;
stat( topFile, &sb );
ret->isSingleFile = !S_ISDIR( sb.st_mode );
}
/* build a list of files containing topFile and,
if it's a directory, all of its children */
if (1) {
char *dir, *base;
char dirbuf[MAX_PATH_LENGTH];
char basebuf[MAX_PATH_LENGTH];
strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
strlcpy( basebuf, topFile, sizeof( basebuf ) );
dir = dirname( dirbuf );
base = basename( basebuf );
files = getFiles( dir, base, NULL );
}
for( walk=files; walk!=NULL; walk=walk->next )
++ret->fileCount;
ret->files = calloc( ret->fileCount, sizeof(char*) );
ret->fileLengths = calloc( ret->fileCount, sizeof(uint64_t) );
for( i=0, walk=files; walk!=NULL; walk=walk->next, ++i )
ret->files[i] = tr_strdup( walk->filename );
qsort( ret->files, ret->fileCount, sizeof(char*), pstrcmp );
for( i=0; i<ret->fileCount; ++i ) {
ret->fileLengths[i] = getFileSize( ret->files[i] );
ret->totalSize += ret->fileLengths[i];
}
freeFileList( files );
ret->pieceSize = bestPieceSize( ret->totalSize );
ret->pieceCount = (int)( ret->totalSize / ret->pieceSize);
if( ret->totalSize % ret->pieceSize )
++ret->pieceCount;
return ret;
}
void
tr_metaInfoBuilderFree( tr_metainfo_builder_t * builder )
{
if( builder != NULL )
{
int i;
for( i=0; i<builder->fileCount; ++i )
tr_free( builder->files[i] );
tr_free( builder->files );
tr_free( builder->fileLengths );
tr_free( builder->top );
tr_free( builder->comment );
tr_free( builder->announce );
tr_free( builder->outputFile );
tr_free( builder );
}
}
/****
*****
****/
static uint8_t*
getHashInfo ( tr_metainfo_builder_t * b )
{
int fileIndex = 0;
uint8_t *ret = (uint8_t*) malloc ( SHA_DIGEST_LENGTH * b->pieceCount );
uint8_t *walk = ret;
uint8_t *buf = malloc( b->pieceSize );
uint64_t totalRemain;
uint64_t off = 0;
FILE * fp;
b->pieceIndex = 0;
totalRemain = b->totalSize;
fp = fopen( b->files[fileIndex], "rb" );
while ( totalRemain )
{
uint8_t *bufptr = buf;
const uint64_t thisPieceSize = MIN( (uint32_t)b->pieceSize, totalRemain );
uint64_t pieceRemain = thisPieceSize;
assert( b->pieceIndex < b->pieceCount );
while( pieceRemain )
{
const uint64_t n_this_pass = MIN( (b->fileLengths[fileIndex] - off), pieceRemain );
fread( bufptr, 1, n_this_pass, fp );
bufptr += n_this_pass;
off += n_this_pass;
pieceRemain -= n_this_pass;
if( off == b->fileLengths[fileIndex] ) {
off = 0;
fclose( fp );
fp = NULL;
if( ++fileIndex < b->fileCount ) {
fp = fopen( b->files[fileIndex], "rb" );
}
}
}
assert( bufptr-buf == (int)thisPieceSize );
assert( pieceRemain == 0 );
SHA1( buf, thisPieceSize, walk );
walk += SHA_DIGEST_LENGTH;
if( b->abortFlag ) {
b->failed = 1;
break;
}
totalRemain -= thisPieceSize;
++b->pieceIndex;
}
assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
assert( b->abortFlag || !totalRemain );
assert( b->abortFlag || fp == NULL );
if( fp != NULL )
fclose( fp );
free( buf );
return ret;
}
static void
getFileInfo( const char * topFile,
const char * filename,
benc_val_t * uninitialized_length,
benc_val_t * uninitialized_path )
{
benc_val_t *sub;
const char *pch, *prev;
const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
int n;
/* get the file size */
tr_bencInitInt( uninitialized_length, getFileSize(filename) );
/* the path list */
n = 1;
for( pch=filename+topLen; *pch; ++pch )
if (*pch == TR_PATH_DELIMITER)
++n;
tr_bencInit( uninitialized_path, TYPE_LIST );
tr_bencListReserve( uninitialized_path, n );
for( prev=pch=filename+topLen; ; ++pch )
{
char buf[MAX_PATH_LENGTH];
if (*pch && *pch!=TR_PATH_DELIMITER )
continue;
memcpy( buf, prev, pch-prev );
buf[pch-prev] = '\0';
sub = tr_bencListAdd( uninitialized_path );
tr_bencInitStrDup( sub, buf );
prev = pch + 1;
if (!*pch)
break;
}
}
static void
makeFilesList( benc_val_t * list,
const tr_metainfo_builder_t * builder )
{
int i = 0;
tr_bencListReserve( list, builder->fileCount );
for( i=0; i<builder->fileCount; ++i )
{
benc_val_t * dict = tr_bencListAdd( list );
benc_val_t *length, *pathVal;
tr_bencInit( dict, TYPE_DICT );
tr_bencDictReserve( dict, 2 );
length = tr_bencDictAdd( dict, "length" );
pathVal = tr_bencDictAdd( dict, "path" );
getFileInfo( builder->top, builder->files[i], length, pathVal );
}
}
static void
makeInfoDict ( benc_val_t * dict,
tr_metainfo_builder_t * builder )
{
uint8_t * pch;
benc_val_t * val;
char base[MAX_PATH_LENGTH];
tr_bencDictReserve( dict, 5 );
val = tr_bencDictAdd( dict, "name" );
strlcpy( base, builder->top, sizeof( base ) );
tr_bencInitStrDup ( val, basename( base ) );
val = tr_bencDictAdd( dict, "piece length" );
tr_bencInitInt( val, builder->pieceSize );
pch = getHashInfo( builder );
val = tr_bencDictAdd( dict, "pieces" );
tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
if ( builder->isSingleFile )
{
val = tr_bencDictAdd( dict, "length" );
tr_bencInitInt( val, builder->fileLengths[0] );
}
else
{
val = tr_bencDictAdd( dict, "files" );
tr_bencInit( val, TYPE_LIST );
makeFilesList( val, builder );
}
val = tr_bencDictAdd( dict, "private" );
tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
}
static void tr_realMakeMetaInfo ( tr_metainfo_builder_t * builder )
{
int n = 5;
benc_val_t top, *val;
tr_bencInit ( &top, TYPE_DICT );
if ( builder->comment && *builder->comment ) ++n;
tr_bencDictReserve( &top, n );
val = tr_bencDictAdd( &top, "announce" );
tr_bencInitStrDup( val, builder->announce );
val = tr_bencDictAdd( &top, "created by" );
tr_bencInitStrDup( val, TR_NAME " " VERSION_STRING );
val = tr_bencDictAdd( &top, "creation date" );
tr_bencInitInt( val, time(0) );
val = tr_bencDictAdd( &top, "encoding" );
tr_bencInitStrDup( val, "UTF-8" );
if( builder->comment && *builder->comment ) {
val = tr_bencDictAdd( &top, "comment" );
tr_bencInitStrDup( val, builder->comment );
}
val = tr_bencDictAdd( &top, "info" );
tr_bencInit( val, TYPE_DICT );
tr_bencDictReserve( val, 666 );
makeInfoDict( val, builder );
/* save the file */
if ( !builder->abortFlag ) {
size_t nmemb;
char * pch = tr_bencSaveMalloc( &top, &n );
FILE * fp = fopen( builder->outputFile, "wb+" );
nmemb = n;
if( fp == NULL )
builder->failed = 1;
else if( fwrite( pch, 1, nmemb, fp ) != nmemb )
builder->failed = 1;
free( pch );
fclose( fp );
}
/* cleanup */
tr_bencFree( & top );
builder->failed |= builder->abortFlag;
builder->isDone = 1;
}
/***
****
**** A threaded builder queue
****
***/
static tr_metainfo_builder_t * queue = NULL;
static int workerIsRunning = 0;
static tr_thread_t workerThread;
static tr_lock_t* getQueueLock( tr_handle_t * h )
{
static tr_lock_t * lock = NULL;
tr_sharedLock( h->shared );
if( lock == NULL )
{
lock = calloc( 1, sizeof( tr_lock_t ) );
tr_lockInit( lock );
}
tr_sharedUnlock( h->shared );
return lock;
}
static void workerFunc( void * user_data )
{
tr_handle_t * handle = (tr_handle_t *) user_data;
for (;;)
{
tr_metainfo_builder_t * builder = NULL;
/* find the next builder to process */
tr_lock_t * lock = getQueueLock ( handle );
tr_lockLock( lock );
if( queue != NULL ) {
builder = queue;
queue = queue->nextBuilder;
}
tr_lockUnlock( lock );
/* if no builders, this worker thread is done */
if( builder == NULL )
break;
tr_realMakeMetaInfo ( builder );
}
workerIsRunning = 0;
}
void
tr_makeMetaInfo( tr_metainfo_builder_t * builder,
const char * outputFile,
const char * announce,
const char * comment,
int isPrivate )
{
tr_lock_t * lock;
builder->abortFlag = 0;
builder->isDone = 0;
builder->announce = tr_strdup( announce );
builder->comment = tr_strdup( comment );
builder->isPrivate = isPrivate;
if( outputFile && *outputFile )
builder->outputFile = tr_strdup( outputFile );
else {
char out[MAX_PATH_LENGTH];
snprintf( out, sizeof(out), "%s.torrent", builder->top);
builder->outputFile = tr_strdup( out );
}
/* enqueue the builder */
lock = getQueueLock ( builder->handle );
tr_lockLock( lock );
builder->nextBuilder = queue;
queue = builder;
if( !workerIsRunning ) {
workerIsRunning = 1;
tr_threadCreate( &workerThread, workerFunc, builder->handle, "makeMeta" );
}
tr_lockUnlock( lock );
}

View File

@ -0,0 +1,91 @@
/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#ifndef TR_MAKEMETA_H
#define TR_MAKEMETA_H 1
typedef struct tr_metainfo_builder_s
{
/**
*** These are set by tr_makeMetaInfo()
*** and cleaned up by tr_metaInfoBuilderFree()
**/
char * top;
char ** files;
uint64_t * fileLengths;
uint64_t totalSize;
int fileCount;
int pieceSize;
int pieceCount;
int isSingleFile;
tr_handle_t * handle;
/**
*** These are set inside tr_makeMetaInfo()
*** by copying the arguments passed to it,
*** and cleaned up by tr_metaInfoBuilderFree()
**/
char * announce;
char * comment;
char * outputFile;
int isPrivate;
/**
*** These are set inside tr_makeMetaInfo() so the client
*** can poll periodically to see what the status is.
*** The client can also set abortFlag to nonzero to
*** tell tr_makeMetaInfo() to abort and clean up after itself.
**/
int pieceIndex;
int abortFlag;
int isDone;
int failed; /* only meaningful if isDone is set */
/**
*** This is an implementation detail.
*** The client should never use these fields.
**/
struct tr_metainfo_builder_s * nextBuilder;
}
tr_metainfo_builder_t;
tr_metainfo_builder_t*
tr_metaInfoBuilderCreate( tr_handle_t * handle,
const char * topFile );
void
tr_metaInfoBuilderFree( tr_metainfo_builder_t* );
/**
* 'outputFile' if NULL, builder->top + ".torrent" will be used.
*
* This is actually done in a worker thread, not the main thread!
* Otherwise the client's interface would lock up while this runs.
*
* It is the caller's responsibility to poll builder->isDone
* from time to time! When the worker thread sets that flag,
* the caller must pass the builder to tr_metaInfoBuilderFree().
*/
void
tr_makeMetaInfo( tr_metainfo_builder_t * builder,
const char * outputFile,
const char * announce,
const char * comment,
int isPrivate );
#endif

View File

@ -239,7 +239,13 @@ realparse( tr_info_t * inf, uint8_t * buf, size_t size )
goto fail;
}
inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH;
inf->pieces = (uint8_t *) tr_bencStealStr( val );
inf->pieces = calloc ( inf->pieceCount, sizeof(tr_piece_t) );
for ( i=0; i<inf->pieceCount; ++i )
{
memcpy (inf->pieces[i].hash, &val->val.s.s[i*SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
}
/* TODO add more tests so we don't crash on weird files */
@ -363,8 +369,8 @@ static int getannounce( tr_info_t * inf, benc_val_t * meta )
if( NULL != val && TYPE_LIST == val->type && 0 < val->val.l.count )
{
inf->trackerTiers = 0;
inf->trackerList = calloc( sizeof( inf->trackerList[0] ),
val->val.l.count );
inf->trackerList = calloc( val->val.l.count,
sizeof( inf->trackerList[0] ) );
/* iterate through the announce-list's tiers */
for( ii = 0; ii < val->val.l.count; ii++ )
@ -375,7 +381,7 @@ static int getannounce( tr_info_t * inf, benc_val_t * meta )
continue;
}
subcount = 0;
sublist = calloc( sizeof( sublist[0] ), subval->val.l.count );
sublist = calloc( subval->val.l.count, sizeof( sublist[0] ) );
/* iterate through the tier's items */
for( jj = 0; jj < subval->val.l.count; jj++ )
@ -411,7 +417,7 @@ static int getannounce( tr_info_t * inf, benc_val_t * meta )
/* if we skipped some of the tier's items then trim the sublist */
else if( 0 < subcount )
{
inf->trackerList[inf->trackerTiers].list = calloc( sizeof( sublist[0] ), subcount );
inf->trackerList[inf->trackerTiers].list = calloc( subcount, sizeof( sublist[0] ) );
memcpy( inf->trackerList[inf->trackerTiers].list, sublist,
sizeof( sublist[0] ) * subcount );
inf->trackerList[inf->trackerTiers].count = subcount;
@ -436,8 +442,8 @@ static int getannounce( tr_info_t * inf, benc_val_t * meta )
else if( inf->trackerTiers < val->val.l.count )
{
swapping = inf->trackerList;
inf->trackerList = calloc( sizeof( inf->trackerList[0] ),
inf->trackerTiers );
inf->trackerList = calloc( inf->trackerTiers,
sizeof( inf->trackerList[0] ) );
memcpy( inf->trackerList, swapping,
sizeof( inf->trackerList[0] ) * inf->trackerTiers );
free( swapping );
@ -461,12 +467,12 @@ static int getannounce( tr_info_t * inf, benc_val_t * meta )
return 1;
}
sublist = calloc( sizeof( sublist[0] ), 1 );
sublist = calloc( 1, sizeof( sublist[0] ) );
sublist[0].address = address;
sublist[0].port = port;
sublist[0].announce = announce;
sublist[0].scrape = announceToScrape( announce );
inf->trackerList = calloc( sizeof( inf->trackerList[0] ), 1 );
inf->trackerList = calloc( 1, sizeof( inf->trackerList[0] ) );
inf->trackerList[0].list = sublist;
inf->trackerList[0].count = 1;
inf->trackerTiers = 1;

View File

@ -423,7 +423,8 @@ int tr_peerPulse( tr_peer_t * peer )
}
/* Disconnect if seeder and torrent is seeding */
if( peer->tor->status == TR_STATUS_SEED && peer->progress >= 1.0 )
if( ( peer->progress >= 1.0 )
&& ( peer->tor->status & (TR_STATUS_SEED|TR_STATUS_DONE) ) )
{
return TR_ERROR;
}
@ -553,8 +554,9 @@ writeBegin:
writeEnd:
/* Ask for a block whenever possible */
if( !tr_cpIsSeeding( tor->completion ) &&
!peer->amInterested && tor->peerCount > TR_MAX_PEER_COUNT - 2 )
if( tr_cpGetStatus( tor->completion ) == TR_CP_INCOMPLETE
&& !peer->amInterested
&& tor->peerCount > TR_MAX_PEER_COUNT - 2 )
{
/* This peer is no use to us, and it seems there are
more */
@ -562,18 +564,41 @@ writeEnd:
return TR_ERROR;
}
if( peer->amInterested && !peer->peerChoking && !peer->banned )
if( peer->amInterested
&& !peer->peerChoking
&& !peer->banned
&& peer->inRequestCount < OUR_REQUEST_COUNT )
{
int block;
while( peer->inRequestCount < OUR_REQUEST_COUNT )
{
block = chooseBlock( tor, peer );
if( block < 0 )
{
break;
}
sendRequest( tor, peer, block );
int i;
int poolSize=0, endgame=0;
int * pool = getPreferredPieces ( tor, peer, &poolSize, &endgame );
/* TODO: add some asserts to see if this bitfield is really necessary */
tr_bitfield_t * blocksAlreadyRequested = tr_bitfieldNew( tor->blockCount );
for( i=0; i<peer->inRequestCount; ++i) {
const tr_request_t * r = &peer->inRequests[i];
const int block = tr_block( r->index, r->begin );
tr_bitfieldAdd( blocksAlreadyRequested, block );
}
for( i=0; i<poolSize && peer->inRequestCount<OUR_REQUEST_COUNT; )
{
int unused;
const int piece = pool[i];
const int block = endgame
? tr_cpMostMissingBlockInPiece( tor->completion, piece, &unused)
: tr_cpMissingBlockInPiece ( tor->completion, piece );
if( block>=0 && (endgame || !tr_bitfieldHas( blocksAlreadyRequested, block ) ) )
{
tr_bitfieldAdd( blocksAlreadyRequested, block );
sendRequest( tor, peer, block );
}
else ++i;
}
tr_bitfieldFree( blocksAlreadyRequested );
free( pool );
}
return TR_OK;

View File

@ -285,8 +285,8 @@ static void sendHave( tr_peer_t * peer, int piece )
**********************************************************************/
static void sendBitfield( tr_torrent_t * tor, tr_peer_t * peer )
{
uint8_t * p;
tr_bitfield_t * bitfield;
uint8_t * p;
const tr_bitfield_t * bitfield;
bitfield = tr_cpPieceBitfield( tor->completion );
p = getMessagePointer( peer, bitfield->len, PEER_MSG_BITFIELD );

View File

@ -173,7 +173,7 @@ static inline int parseBitfield( tr_torrent_t * tor, tr_peer_t * peer,
{
peer->bitfield = tr_bitfieldNew( inf->pieceCount );
}
assert( bitfieldSize == peer->bitfield->len );
assert( (unsigned)bitfieldSize == peer->bitfield->len );
memcpy( peer->bitfield->bits, p, bitfieldSize );
peer->pieceCount = 0;

View File

@ -185,16 +185,35 @@ static int checkPeer( tr_peer_t * peer )
return TR_OK;
}
static int isPieceInteresting( const tr_torrent_t * tor,
const tr_peer_t * peer,
int piece )
{
if( tor->info.pieces[piece].priority == TR_PRI_DND ) /* we don't want it */
return 0;
if( !tr_bitfieldHas( peer->bitfield, piece ) ) /* peer doesn't have it */
return 0;
if( tr_bitfieldHas( peer->banfield, piece ) ) /* peer is banned for it */
return 0;
if( tr_cpPieceIsComplete( tor->completion, piece ) ) /* we already have it */
return 0;
return 1;
}
/***********************************************************************
* isInteresting
***********************************************************************
* Returns 1 if 'peer' has at least one piece that we haven't completed,
* or 0 otherwise.
* Returns 1 if 'peer' has at least one piece that we want but
* haven't completed, or 0 otherwise.
**********************************************************************/
static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer )
static int isInteresting( const tr_torrent_t * tor, const tr_peer_t * peer )
{
int ii;
tr_bitfield_t * bitfield = tr_cpPieceBitfield( tor->completion );
int i;
const tr_bitfield_t * bitfield = tr_cpPieceBitfield( tor->completion );
if( !peer->bitfield )
{
@ -203,13 +222,10 @@ static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer )
}
assert( bitfield->len == peer->bitfield->len );
for( ii = 0; ii < bitfield->len; ii++ )
{
if( ( peer->bitfield->bits[ii] & ~(bitfield->bits[ii]) ) & 0xFF )
{
for( i=0; i<tor->info.pieceCount; ++i )
if( isPieceInteresting( tor, peer, i ) )
return 1;
}
}
return 0;
}
@ -227,161 +243,103 @@ static void updateInterest( tr_torrent_t * tor, tr_peer_t * peer )
}
}
/***********************************************************************
* chooseBlock
***********************************************************************
* At this point, we know the peer has at least one block we have an
* interest in. If he has more than one, we choose which one we are
* going to ask first.
* Our main goal is to complete pieces, so we look the pieces which are
* missing less blocks.
**********************************************************************/
static inline int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer )
/** utility structure used by getPreferredPieces() and comparePieces() */
typedef struct
{
tr_info_t * inf = &tor->info;
int piece;
tr_priority_t priority;
int missingBlockCount;
int peerCount;
}
PieceCompareData;
/** utility function used by getPreferredPieces */
int comparePieces (const void * aIn, const void * bIn)
{
const PieceCompareData * a = (const PieceCompareData*) aIn;
const PieceCompareData * b = (const PieceCompareData*) bIn;
/* if one piece has a higher priority, it goes first */
if (a->priority != b->priority)
return a->priority > b->priority ? -1 : 1;
/* otherwise if one has fewer missing blocks, it goes first */
if (a->missingBlockCount != b->missingBlockCount)
return a->missingBlockCount < b->missingBlockCount ? -1 : 1;
/* otherwise if one has fewer peers, it goes first */
if (a->peerCount != b->peerCount)
return a->peerCount < b->peerCount ? -1 : 1;
/* otherwise go with the earlier piece */
return a->piece - b->piece;
}
static int* getPreferredPieces( const tr_torrent_t * tor,
const tr_peer_t * peer,
int * pieceCount,
int * endgame )
{
const tr_info_t * inf = &tor->info;
int i;
int missingBlocks, minMissing;
int poolSize, * pool;
int block, minDownloading;
int poolSize = 0;
int * pool = malloc ( inf->pieceCount * sizeof(int) );
/* Choose a piece */
pool = malloc( inf->pieceCount * sizeof( int ) );
poolSize = 0;
minMissing = tor->blockCount + 1;
for( i = 0; i < inf->pieceCount; i++ )
{
missingBlocks = tr_cpMissingBlocksForPiece( tor->completion, i );
if( missingBlocks < 1 )
{
/* We already have or are downloading all blocks */
continue;
}
if( !tr_bitfieldHas( peer->bitfield, i ) )
{
/* The peer doesn't have this piece */
continue;
}
if( peer->banfield && tr_bitfieldHas( peer->banfield, i ) )
{
/* The peer is banned for this piece */
continue;
}
*endgame = 0;
/* We are interested in this piece, remember it */
if( missingBlocks < minMissing )
{
minMissing = missingBlocks;
poolSize = 0;
}
if( missingBlocks <= minMissing )
{
pool[poolSize++] = i;
}
for( i=0; i<inf->pieceCount; ++i )
if( isPieceInteresting( tor, peer, i ) )
if( tr_cpMissingBlocksForPiece( tor->completion, i ) )
pool[poolSize++] = i;
if( !poolSize ) {
*endgame = 1;
for( i=0; i<inf->pieceCount; ++i )
if( isPieceInteresting( tor, peer, i ) )
pool[poolSize++] = i;
}
if( poolSize )
#if 0
fprintf (stderr, "old pool: ");
for (i=0; i<15 && i<poolSize; ++i ) fprintf (stderr, "%d, ", pool[i] );
fprintf (stderr, "\n");
#endif
/* sort the rest from most interesting to least */
if( poolSize > 1 )
{
/* All pieces in 'pool' have 'minMissing' missing blocks. Find
the rarest ones. */
tr_bitfield_t * bitfield;
int piece;
int min, foo, j;
int * pool2;
int pool2Size;
PieceCompareData * p = malloc ( poolSize * sizeof(PieceCompareData) );
pool2 = malloc( poolSize * sizeof( int ) );
pool2Size = 0;
min = TR_MAX_PEER_COUNT + 1;
for( i = 0; i < poolSize; i++ )
for( i=0; i<poolSize; ++i )
{
foo = 0;
for( j = 0; j < tor->peerCount; j++ )
{
bitfield = tor->peers[j]->bitfield;
if( bitfield && tr_bitfieldHas( bitfield, pool[i] ) )
{
foo++;
}
}
if( foo < min )
{
min = foo;
pool2Size = 0;
}
if( foo <= min )
{
pool2[pool2Size++] = pool[i];
}
}
free( pool );
int j;
const int piece = pool[i];
if( pool2Size < 1 )
{
/* Shouldn't happen */
free( pool2 );
return -1;
p[i].piece = piece;
p[i].priority = inf->pieces[piece].priority;
p[i].missingBlockCount = tr_cpMissingBlocksForPiece( tor->completion, piece );
p[i].peerCount = 0;
for( j=0; j<tor->peerCount; ++j )
if( tr_bitfieldHas( tor->peers[j]->bitfield, piece ) )
++p[i].peerCount;
}
/* All pieces in pool2 have the same number of missing blocks,
and are availabme from the same number of peers. Pick a
random one */
piece = pool2[tr_rand(pool2Size)];
free( pool2 );
qsort (p, poolSize, sizeof(PieceCompareData), comparePieces);
/* Pick a block in this piece */
block = tr_cpMissingBlockInPiece( tor->completion, piece );
goto check;
for( i=0; i<poolSize; ++i )
pool[i] = p[i].piece;
free( p );
}
free( pool );
#if 0
fprintf (stderr, "new pool: ");
for (i=0; i<15 && i<poolSize; ++i ) fprintf (stderr, "%d, ", pool[i] );
fprintf (stderr, "\n");
#endif
/* "End game" mode */
minDownloading = 255;
block = -1;
for( i = 0; i < inf->pieceCount; i++ )
{
int downloaders, block2;
if( !tr_bitfieldHas( peer->bitfield, i ) )
{
/* The peer doesn't have this piece */
continue;
}
if( peer->banfield && tr_bitfieldHas( peer->banfield, i ) )
{
/* The peer is banned for this piece */
continue;
}
if( tr_cpPieceIsComplete( tor->completion, i ) )
{
/* We already have it */
continue;
}
block2 = tr_cpMostMissingBlockInPiece( tor->completion, i, &downloaders );
if( block2 > -1 && downloaders < minDownloading )
{
block = block2;
minDownloading = downloaders;
}
}
check:
if( block < 0 )
{
/* Shouldn't happen */
return -1;
}
for( i = 0; i < peer->inRequestCount; i++ )
{
tr_request_t * r;
r = &peer->inRequests[i];
if( tr_block( r->index, r->begin ) == block )
{
/* We are already asking this peer for this block */
return -1;
}
}
return block;
*pieceCount = poolSize;
return pool;
}

View File

@ -24,6 +24,7 @@
#include "transmission.h"
#include "shared.h"
#define INTERVAL_MSEC 100
/***********************************************************************
* Local prototypes
@ -171,7 +172,7 @@ static tr_torrent_t * torrentRealInit( tr_handle_t * h, tr_torrent_t * tor,
tor->id = h->id;
tor->key = h->key;
tor->azId = h->azId;
tor->finished = 0;
tor->hasChangedState = -1;
/* Escaped info hash for HTTP queries */
for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
@ -215,6 +216,8 @@ static tr_torrent_t * torrentRealInit( tr_handle_t * h, tr_torrent_t * tor,
tr_setBindPort( h, TR_DEFAULT_PORT );
}
tr_torrentInitFilePieces( tor );
return tor;
}
@ -240,7 +243,7 @@ void tr_torrentSetFolder( tr_torrent_t * tor, const char * path )
}
}
const char * tr_torrentGetFolder( tr_torrent_t * tor )
char * tr_torrentGetFolder( tr_torrent_t * tor )
{
return tor->destination;
}
@ -335,8 +338,8 @@ static void torrentReallyStop( tr_torrent_t * tor )
if( tor->tracker )
{
tr_trackerClose( tor->tracker );
tor->tracker = NULL;
tr_trackerClose( tor->tracker );
tor->tracker = NULL;
}
tr_lockLock( &tor->lock );
@ -374,16 +377,29 @@ void tr_torrentDisablePex( tr_torrent_t * tor, int disable )
tr_lockUnlock( &tor->lock );
}
int tr_getFinished( tr_torrent_t * tor )
static int tr_didStateChangeTo ( tr_torrent_t * tor, int status )
{
if( tor->finished )
if( tor->hasChangedState == status )
{
tor->finished = 0;
tor->hasChangedState = -1;
return 1;
}
return 0;
}
int tr_getIncomplete( tr_torrent_t * tor )
{
return tr_didStateChangeTo( tor, TR_CP_INCOMPLETE );
}
int tr_getDone( tr_torrent_t * tor )
{
return tr_didStateChangeTo( tor, TR_CP_DONE );
}
int tr_getComplete( tr_torrent_t * tor )
{
return tr_didStateChangeTo( tor, TR_CP_COMPLETE );
}
void tr_manualUpdate( tr_torrent_t * tor )
{
int peerCount, new;
@ -459,8 +475,10 @@ tr_stat_t * tr_torrentStat( tr_torrent_t * tor )
}
}
s->progress = tr_cpCompletionAsFloat( tor->completion );
s->left = tr_cpLeftBytes( tor->completion );
s->percentDone = tr_cpPercentDone( tor->completion );
s->percentComplete = tr_cpPercentComplete( tor->completion );
s->cpStatus = tr_cpGetStatus( tor->completion );
s->left = tr_cpLeftUntilDone( tor->completion );
if( tor->status & TR_STATUS_DOWNLOAD )
{
s->rateDownload = tr_rcRate( tor->download );
@ -493,16 +511,18 @@ tr_stat_t * tr_torrentStat( tr_torrent_t * tor )
s->eta = (float) s->left / s->rateDownload / 1024.0;
}
s->downloaded = tor->downloadedCur + tor->downloadedPrev;
s->uploaded = tor->uploadedCur + tor->uploadedPrev;
s->uploaded = tor->uploadedCur + tor->uploadedPrev;
s->downloaded = tor->downloadedCur + tor->downloadedPrev;
s->downloadedValid = tr_cpDownloadedValid( tor->completion );
if( s->downloaded == 0 && s->progress == 0.0 )
if( s->downloaded == 0 && s->percentDone == 0.0 )
{
s->ratio = TR_RATIO_NA;
}
else
{
s->ratio = (float)s->uploaded / (float)MAX(s->downloaded, inf->totalSize - s->left);
s->ratio = (float)s->uploaded
/ (float)MAX(s->downloaded, s->downloadedValid);
}
tr_lockUnlock( &tor->lock );
@ -596,53 +616,69 @@ void tr_torrentAvailability( tr_torrent_t * tor, int8_t * tab, int size )
tr_lockUnlock( &tor->lock );
}
float * tr_torrentCompletion( tr_torrent_t * tor )
size_t
tr_torrentFileBytesCompleted ( const tr_torrent_t * tor, int fileIndex )
{
tr_info_t * inf = &tor->info;
int piece, file;
float * ret, prog, weight;
uint64_t piecemax, piecesize;
uint64_t filestart, fileoff, filelen, blockend, blockused;
const tr_file_t * file = &tor->info.files[fileIndex];
const int firstBlock = file->offset / tor->blockSize;
const int firstBlockOffset = file->offset % tor->blockSize;
const int lastOffset = file->length ? file->length-1 : 0;
const int lastBlock = (file->offset + lastOffset) / tor->blockSize;
const int lastBlockOffset = (file->offset + lastOffset) % tor->blockSize;
size_t haveBytes = 0;
tr_lockLock( &tor->lock );
assert( tor != NULL );
assert( 0<=fileIndex && fileIndex<tor->info.fileCount );
assert( file->offset + file->length <= tor->info.totalSize );
assert( 0<=firstBlock && firstBlock<tor->blockCount );
assert( 0<=lastBlock && lastBlock<tor->blockCount );
assert( firstBlock <= lastBlock );
assert( tr_blockPiece( firstBlock ) == file->firstPiece );
assert( tr_blockPiece( lastBlock ) == file->lastPiece );
ret = calloc( inf->fileCount, sizeof( float ) );
file = 0;
piecemax = inf->pieceSize;
filestart = 0;
fileoff = 0;
piece = 0;
while( inf->pieceCount > piece )
if( firstBlock == lastBlock )
{
assert( file < inf->fileCount );
assert( filestart + fileoff < inf->totalSize );
filelen = inf->files[file].length;
piecesize = tr_pieceSize( piece );
blockend = MIN( filestart + filelen, piecemax * piece + piecesize );
blockused = blockend - ( filestart + fileoff );
weight = ( filelen ? ( float )blockused / ( float )filelen : 1.0 );
prog = tr_cpPercentBlocksInPiece( tor->completion, piece );
ret[file] += prog * weight;
fileoff += blockused;
assert( -0.1 < prog && 1.1 > prog );
assert( -0.1 < weight && 1.1 > weight );
if( fileoff == filelen )
{
ret[file] = MIN( 1.0, ret[file] );
ret[file] = MAX( 0.0, ret[file] );
filestart += fileoff;
fileoff = 0;
file++;
}
if( filestart + fileoff >= piecemax * piece + piecesize )
{
piece++;
}
if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
haveBytes += lastBlockOffset + 1 - firstBlockOffset;
}
else
{
int i;
if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
haveBytes += tor->blockSize - firstBlockOffset;
for( i=firstBlock+1; i<lastBlock; ++i )
if( tr_cpBlockIsComplete( tor->completion, i ) )
haveBytes += tor->blockSize;
if( tr_cpBlockIsComplete( tor->completion, lastBlock ) )
haveBytes += lastBlockOffset + 1;
}
return haveBytes;
}
float
tr_torrentFileCompletion ( const tr_torrent_t * tor, int fileIndex )
{
const size_t c = tr_torrentFileBytesCompleted ( tor, fileIndex );
return (float)c / tor->info.files[fileIndex].length;
}
float*
tr_torrentCompletion( tr_torrent_t * tor )
{
int i;
float * f;
tr_lockLock( &tor->lock );
f = calloc ( tor->info.fileCount, sizeof( float ) );
for( i=0; i<tor->info.fileCount; ++i )
f[i] = tr_torrentFileCompletion ( tor, i );
tr_lockUnlock( &tor->lock );
return ret;
return f;
}
void tr_torrentAmountFinished( tr_torrent_t * tor, float * tab, int size )
@ -917,30 +953,43 @@ static void downloadLoop( void * _tor )
tr_torrent_t * tor = _tor;
int i, ret;
int peerCount, used;
cp_status_t cpState, cpPrevState;
uint8_t * peerCompact;
tr_peer_t * peer;
tr_dbg ("torrent %s has its own thread", tor->info.name);
tr_lockLock( &tor->lock );
tor->status = tr_cpIsSeeding( tor->completion ) ?
TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
cpState = cpPrevState = tr_cpGetStatus( tor->completion );
switch( cpState ) {
case TR_CP_COMPLETE: tor->status = TR_STATUS_SEED; break;
case TR_CP_DONE: tor->status = TR_STATUS_DONE; break;
case TR_CP_INCOMPLETE: tor->status = TR_STATUS_DOWNLOAD; break;
}
while( !tor->die )
{
tr_lockUnlock( &tor->lock );
tr_wait( 20 );
tr_wait( INTERVAL_MSEC );
tr_lockLock( &tor->lock );
/* Are we finished ? */
if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
tr_cpIsSeeding( tor->completion ) )
cpState = tr_cpGetStatus( tor->completion );
if( cpState != cpPrevState )
{
/* Done */
tor->status = TR_STATUS_SEED;
tor->finished = 1;
tr_trackerCompleted( tor->tracker );
switch( cpState ) {
case TR_CP_COMPLETE: tor->status = TR_STATUS_SEED; break;
case TR_CP_DONE: tor->status = TR_STATUS_DONE; break;
case TR_CP_INCOMPLETE: tor->status = TR_STATUS_DOWNLOAD; break;
}
tor->hasChangedState = cpState;
if( cpState == TR_CP_COMPLETE )
tr_trackerCompleted( tor->tracker );
tr_ioSync( tor->io );
cpPrevState = cpState;
}
/* Try to get new peers or to send a message to the tracker */
@ -1016,3 +1065,123 @@ static void downloadLoop( void * _tor )
tor->status = TR_STATUS_STOPPED;
}
/***
****
**** File prioritization
****
***/
static int
getBytePiece( const tr_info_t * info, uint64_t byteOffset )
{
assert( info != NULL );
assert( info->pieceSize != 0 );
return byteOffset / info->pieceSize;
}
static void
initFilePieces ( tr_info_t * info, int fileIndex )
{
tr_file_t * file = &info->files[fileIndex];
uint64_t firstByte, lastByte;
assert( info != NULL );
assert( 0<=fileIndex && fileIndex<info->fileCount );
file = &info->files[fileIndex];
firstByte = file->offset;
lastByte = firstByte + (file->length ? file->length-1 : 0);
file->firstPiece = getBytePiece( info, firstByte );
file->lastPiece = getBytePiece( info, lastByte );
tr_dbg( "file #%d is in pieces [%d...%d] (%s)", fileIndex, file->firstPiece, file->lastPiece, file->name );
}
static tr_priority_t
calculatePiecePriority ( const tr_torrent_t * tor,
int piece )
{
int i;
tr_priority_t priority = TR_PRI_DND;
for( i=0; i<tor->info.fileCount; ++i )
{
const tr_file_t * file = &tor->info.files[i];
if ( file->firstPiece <= piece
&& file->lastPiece >= piece
&& file->priority > priority)
priority = file->priority;
}
return priority;
}
void
tr_torrentInitFilePieces( tr_torrent_t * tor )
{
int i;
uint64_t offset = 0;
assert( tor != NULL );
for( i=0; i<tor->info.fileCount; ++i ) {
tor->info.files[i].offset = offset;
offset += tor->info.files[i].length;
initFilePieces( &tor->info, i );
}
for( i=0; i<tor->info.pieceCount; ++i )
tor->info.pieces[i].priority = calculatePiecePriority( tor, i );
}
void
tr_torrentSetFilePriority( tr_torrent_t * tor,
int fileIndex,
tr_priority_t priority )
{
int i;
tr_file_t * file;
assert( tor != NULL );
assert( 0<=fileIndex && fileIndex<tor->info.fileCount );
assert( priority==TR_PRI_LOW || priority==TR_PRI_NORMAL
|| priority==TR_PRI_HIGH || priority==TR_PRI_DND );
file = &tor->info.files[fileIndex];
file->priority = priority;
for( i=file->firstPiece; i<=file->lastPiece; ++i )
tor->info.pieces[i].priority = calculatePiecePriority( tor, i );
tr_dbg ( "Setting file #%d (pieces %d-%d) priority to %d (%s)",
fileIndex, file->firstPiece, file->lastPiece,
priority, tor->info.files[fileIndex].name );
}
tr_priority_t
tr_torrentGetFilePriority( const tr_torrent_t * tor, int file )
{
assert( tor != NULL );
assert( 0<=file && file<tor->info.fileCount );
return tor->info.files[file].priority;
}
void
tr_torrentSetFilePriorities( tr_torrent_t * tor,
const tr_priority_t * filePriorities )
{
int i;
for( i=0; i<tor->info.pieceCount; ++i )
tr_torrentSetFilePriority( tor, i, filePriorities[i] );
}
tr_priority_t*
tr_torrentGetFilePriorities( const tr_torrent_t * tor )
{
int i;
tr_priority_t * p = malloc( tor->info.fileCount * sizeof(tr_priority_t) );
for( i=0; i<tor->info.fileCount; ++i )
p[i] = tor->info.files[i].priority;
return p;
}

View File

@ -236,7 +236,8 @@ static int shouldConnect( tr_tracker_t * tc )
/* If there is quite a lot of people on this torrent, stress
the tracker a bit until we get a decent number of peers */
if( tc->hasManyPeers && !tr_cpIsSeeding( tor->completion ) )
if( tc->hasManyPeers &&
(tr_cpGetStatus ( tor->completion ) == TR_CP_INCOMPLETE ))
{
/* reannounce in 10 seconds if we have less than 5 peers */
if( tor->peerCount < 5 )
@ -558,7 +559,7 @@ static tr_http_t * getQuery( tr_tracker_t * tc )
}
start = ( strchr( tcInf->announce, '?' ) ? '&' : '?' );
left = tr_cpLeftBytes( tor->completion );
left = tr_cpLeftUntilComplete( tor->completion );
return tr_httpClient( TR_HTTP_GET, tcInf->address, tcInf->port,
"%s%c"

View File

@ -52,6 +52,9 @@ extern "C" {
#define INET_ADDRSTRLEN 16
#endif
#define TR_PATH_DELIMITER '/'
#define TR_PATH_DELIMITER_STR "/"
#define TR_DEFAULT_PORT 9090
#define TR_PEER_FROM__MAX 4
@ -177,6 +180,7 @@ void tr_setGlobalDownloadLimit( tr_handle_t *, int );
**********************************************************************/
int tr_torrentCount( tr_handle_t * h );
/***********************************************************************
* tr_torrentIterate
***********************************************************************
@ -190,6 +194,44 @@ void tr_setUseCustomLimit( tr_torrent_t * tor, int limit );
void tr_setUploadLimit( tr_torrent_t * tor, int limit );
void tr_setDownloadLimit( tr_torrent_t * tor, int limit );
/***********************************************************************
* Torrent Priorities
**********************************************************************/
enum
{
TR_PRI_DND = -2, /* Do Not Download */
TR_PRI_LOW = -1,
TR_PRI_NORMAL = 0, /* since NORMAL is 0, memset initializes nicely */
TR_PRI_HIGH = 1
};
typedef int8_t tr_priority_t;
void tr_torrentInitFilePieces( tr_torrent_t * tor );
/* priorities should be an array of tor->info.fileCount bytes,
* each holding a value of TR_PRI_NORMAL, _HIGH, _LOW, or _DND. */
void tr_torrentSetFilePriorities ( tr_torrent_t *, const tr_priority_t * priorities );
/* single-file form of tr_torrentPrioritizeFiles.
* priority must be one of TR_PRI_NORMAL, _HIGH, _LOW, or _DND */
void tr_torrentSetFilePriority( tr_torrent_t *, int file, tr_priority_t priority );
/* returns a malloc()ed array of tor->info.fileCount items,
* each holding a value of TR_PRI_NORMAL, _HIGH, _LOW, or _DND.
free the array when done. */
tr_priority_t* tr_torrentGetFilePriorities( const tr_torrent_t * );
/* single-file form of tr_torrentGetFilePriorities.
* returns one of TR_PRI_NORMAL, _HIGH, _LOW, or _DND */
tr_priority_t tr_torrentGetFilePriority( const tr_torrent_t *, int file );
/* returns the priority for the specified piece,
* as ranked by the number of files of various priorities in that piece.
* a zero priority means DND */
tr_priority_t tr_torrentGetPiecePriority( const tr_torrent_t *, int pieceIndex );
/***********************************************************************
* tr_torrentRates
***********************************************************************
@ -270,7 +312,7 @@ tr_info_t * tr_torrentInfo( tr_torrent_t * );
int tr_torrentScrape( tr_torrent_t *, int * s, int * l, int * d );
void tr_torrentSetFolder( tr_torrent_t *, const char * );
const char * tr_torrentGetFolder( tr_torrent_t * );
char * tr_torrentGetFolder( tr_torrent_t * );
int tr_torrentDuplicateDownload( tr_torrent_t * tor );
@ -296,12 +338,15 @@ void tr_torrentStart( tr_torrent_t * );
void tr_torrentStop( tr_torrent_t * );
/***********************************************************************
* tr_getFinished
* tr_getComplete, tr_getIncomplete and tr_getPartial
***********************************************************************
* The first call after a torrent is completed returns 1. Returns 0
* The first call after a torrent changed state returns 1. Returns 0
* in other cases.
**********************************************************************/
int tr_getFinished( tr_torrent_t * );
int tr_getIncomplete( tr_torrent_t * tor );
int tr_getDone( tr_torrent_t * tor );
int tr_getComplete( tr_torrent_t * tor );
/***********************************************************************
* tr_manualUpdate
@ -349,7 +394,12 @@ void tr_torrentAmountFinished( tr_torrent_t * tor, float * tab, int size );
* array of floats the same size and order as in tr_info_t. Free the
* array when done.
**********************************************************************/
float * tr_torrentCompletion( tr_torrent_t * tor );
float * tr_torrentCompletion( tr_torrent_t * );
float tr_torrentFileCompletion( const tr_torrent_t *, int fileIndex );
size_t tr_torrentFileBytesCompleted( const tr_torrent_t *, int fileIndex );
/***********************************************************************
* tr_torrentRemoveSaved
@ -372,12 +422,25 @@ void tr_torrentClose( tr_torrent_t * );
/***********************************************************************
* tr_info_s
**********************************************************************/
typedef struct tr_file_s
{
uint64_t length; /* Length of the file, in bytes */
char name[MAX_PATH_LENGTH]; /* Path to the file */
int8_t priority; /* TR_PRI_HIGH, _NORMAL, _LOW, or _DND */
int firstPiece; /* We need pieces [firstPiece... */
int lastPiece; /* ...lastPiece] to dl this file */
uint64_t offset; /* file begins at the torrent's nth byte */
}
tr_file_t;
typedef struct tr_piece_s
{
uint8_t hash[SHA_DIGEST_LENGTH]; /* pieces hash */
int8_t priority; /* TR_PRI_HIGH, _NORMAL, _LOW, or _DND */
}
tr_piece_t;
struct tr_info_s
{
/* Path to torrent */
@ -410,7 +473,7 @@ struct tr_info_s
int pieceSize;
int pieceCount;
uint64_t totalSize;
uint8_t * pieces;
tr_piece_t * pieces;
/* Files info */
int multifile;
@ -418,23 +481,35 @@ struct tr_info_s
tr_file_t * files;
};
typedef enum
{
TR_CP_COMPLETE, /* has every piece */
TR_CP_DONE, /* has all the pieces but the DND ones */
TR_CP_INCOMPLETE /* doesn't have all the desired pieces */
}
cp_status_t;
/***********************************************************************
* tr_stat_s
**********************************************************************/
struct tr_stat_s
{
#define TR_STATUS_CHECK_WAIT 0x001 /* Waiting in queue to check files */
#define TR_STATUS_CHECK 0x002 /* Checking files */
#define TR_STATUS_DOWNLOAD 0x004 /* Downloading */
#define TR_STATUS_SEED 0x008 /* Seeding */
#define TR_STATUS_STOPPING 0x010 /* Sending 'stopped' to the tracker */
#define TR_STATUS_STOPPED 0x020 /* Sent 'stopped' but thread still
running (for internal use only) */
#define TR_STATUS_PAUSE 0x040 /* Paused */
#define TR_STATUS_CHECK_WAIT (1<<0) /* Waiting in queue to check files */
#define TR_STATUS_CHECK (1<<1) /* Checking files */
#define TR_STATUS_DOWNLOAD (1<<2) /* Downloading */
#define TR_STATUS_DONE (1<<3) /* not at 100% so can't tell the tracker
we're a seeder, but due to DND files
there's nothing we want right now */
#define TR_STATUS_SEED (1<<4) /* Seeding */
#define TR_STATUS_STOPPING (1<<5) /* Sending 'stopped' to the tracker */
#define TR_STATUS_STOPPED (1<<6) /* Sent 'stopped' but thread still
running (for internal use only) */
#define TR_STATUS_PAUSE (1<<7) /* Paused */
#define TR_STATUS_ACTIVE (TR_STATUS_CHECK_WAIT|TR_STATUS_CHECK|TR_STATUS_DOWNLOAD|TR_STATUS_SEED)
#define TR_STATUS_ACTIVE (TR_STATUS_CHECK_WAIT|TR_STATUS_CHECK|TR_STATUS_DOWNLOAD|TR_STATUS_DONE|TR_STATUS_SEED)
#define TR_STATUS_INACTIVE (TR_STATUS_STOPPING|TR_STATUS_STOPPED|TR_STATUS_PAUSE)
int status;
cp_status_t cpStatus;
int error;
char errorString[128];
@ -442,7 +517,8 @@ struct tr_stat_s
tr_tracker_info_t * tracker;
float progress;
float percentComplete;
float percentDone;
float rateDownload;
float rateUpload;
int eta;
@ -456,6 +532,7 @@ struct tr_stat_s
uint64_t left;
uint64_t downloaded;
uint64_t downloadedValid;
uint64_t uploaded;
float swarmspeed;

View File

@ -22,6 +22,7 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include <ctype.h>
#include "transmission.h"
#define SPRINTF_BUFSIZE 100
@ -161,32 +162,22 @@ int tr_rand( int sup )
return rand() % sup;
}
void * tr_memmem( const void *vbig, size_t big_len,
const void *vlittle, size_t little_len )
void*
tr_memmem( const void* haystack, size_t hl,
const void* needle, size_t nl)
{
const char *big = vbig;
const char *little = vlittle;
size_t ii, jj;
const char *walk, *end;
if( 0 == big_len || 0 == little_len )
{
if( !nl )
return (void*) haystack;
if( hl < nl )
return NULL;
}
for( ii = 0; ii + little_len <= big_len; ii++ )
{
for( jj = 0; jj < little_len; jj++ )
{
if( big[ii + jj] != little[jj] )
{
break;
}
}
if( jj == little_len )
{
return (char*)big + ii;
}
}
for (walk=(const char*)haystack, end=walk+hl-nl; walk!=end; ++walk)
if( !memcmp( walk, needle, nl ) )
return (void*) walk;
return NULL;
}
@ -242,44 +233,20 @@ 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
tr_strncasecmp( const char * s1, const char * s2, size_t n )
{
int ii;
char firstchar, secondchar;
if ( !n )
return 0;
if( 0 > len )
{
len = strlen( first );
ii = strlen( second );
len = MIN( len, ii );
while( n-- != 0 && tolower( *s1 ) == tolower( *s2 ) ) {
if( !n || !*s1 || !*s2 )
break;
++s1;
++s2;
}
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;
return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
}
int tr_sprintf( char ** buf, int * used, int * max, const char * format, ... )
@ -362,20 +329,20 @@ tr_dupstr( const char * base, int len )
int
tr_ioErrorFromErrno( void )
{
if( EACCES == errno || EROFS == errno )
switch( errno )
{
return TR_ERROR_IO_PERMISSIONS;
case EACCES:
case EROFS:
return TR_ERROR_IO_PERMISSIONS;
case ENOSPC:
return TR_ERROR_IO_SPACE;
case EMFILE:
case EFBIG:
return TR_ERROR_IO_RESOURCES;
default:
tr_dbg( "generic i/o errno from errno: %s", strerror( errno ) );
return TR_ERROR_IO_OTHER;
}
else if( ENOSPC == errno )
{
return TR_ERROR_IO_SPACE;
}
else if( EMFILE == errno || EFBIG == errno )
{
return TR_ERROR_IO_RESOURCES;
}
tr_dbg( "generic i/o errno from errno: %s", strerror( errno ) );
return TR_ERROR_IO_OTHER;
}
char *
@ -405,3 +372,192 @@ tr_errorString( int code )
return "Unknown error";
}
/****
*****
****/
char* tr_strdup( const char * in )
{
char * out = NULL;
if( in != NULL )
{
const size_t len = strlen( in );
out = malloc( len + 1 );
memcpy( out, in, len );
out[len] = '\0';
}
return out;
}
void*
tr_malloc( size_t size )
{
return size ? malloc( size ) : NULL;
}
void tr_free( void * p )
{
if( p )
free( p );
}
/****
*****
****/
/* note that the argument is how many bits are needed, not bytes */
tr_bitfield_t*
tr_bitfieldNew( size_t bitcount )
{
tr_bitfield_t * ret = calloc( 1, sizeof(tr_bitfield_t) );
if( NULL == ret )
return NULL;
ret->len = ( bitcount + 7u ) / 8u;
ret->bits = calloc( ret->len, 1 );
if( NULL == ret->bits ) {
free( ret );
return NULL;
}
return ret;
}
tr_bitfield_t*
tr_bitfieldDup( const tr_bitfield_t * in )
{
tr_bitfield_t * ret = calloc( 1, sizeof(tr_bitfield_t) );
ret->len = in->len;
ret->bits = malloc( ret->len );
memcpy( ret->bits, in->bits, ret->len );
return ret;
}
void tr_bitfieldFree( tr_bitfield_t * bitfield )
{
if( bitfield )
{
free( bitfield->bits );
free( bitfield );
}
}
void
tr_bitfieldClear( tr_bitfield_t * bitfield )
{
memset( bitfield->bits, 0, bitfield->len );
}
int
tr_bitfieldIsEmpty( const tr_bitfield_t * bitfield )
{
unsigned int i;
for( i=0; i<bitfield->len; ++i )
if( *bitfield->bits )
return 0;
return 1;
}
int
tr_bitfieldHas( const tr_bitfield_t * bitfield,
size_t bit )
{
if ( bitfield == NULL ) return 0;
assert( bit / 8u < bitfield->len );
return ( bitfield->bits[ bit / 8u ] & ( 1 << ( 7 - ( bit % 8 ) ) ) );
}
void
tr_bitfieldAdd( tr_bitfield_t * bitfield, size_t bit )
{
assert( bit / 8u < bitfield->len );
bitfield->bits[ bit / 8u ] |= ( 1u << ( 7u - ( bit % 8u ) ) );
}
void
tr_bitfieldAddRange( tr_bitfield_t * bitfield,
size_t first,
size_t last )
{
/* TODO: there are faster ways to do this */
unsigned int i;
for( i=first; i<=last; ++i )
tr_bitfieldAdd( bitfield, i );
}
void
tr_bitfieldRem( tr_bitfield_t * bitfield,
size_t bit )
{
assert( bit / 8u < bitfield->len );
bitfield->bits[ bit / 8u ] &= ~( 1u << ( 7u - ( bit % 8u ) ) );
}
void
tr_bitfieldRemRange ( tr_bitfield_t * b,
size_t first,
size_t last )
{
/* TODO: there are faster ways to do this */
unsigned int i;
for( i=first; i<=last; ++i )
tr_bitfieldRem( b, i );
}
tr_bitfield_t*
tr_bitfieldNegate( tr_bitfield_t * b )
{
uint8_t *it;
const uint8_t *end;
for( it=b->bits, end=it+b->len; it!=end; ++it )
*it = ~*it;
return b;
}
tr_bitfield_t*
tr_bitfieldAnd( tr_bitfield_t * a, const tr_bitfield_t * b )
{
uint8_t *ait;
const uint8_t *aend, *bit;
assert( a->len == b->len );
for( ait=a->bits, bit=b->bits, aend=ait+a->len; ait!=aend; ++ait, ++bit )
*ait &= *bit;
return a;
}
size_t
tr_bitfieldCountTrueBits( const tr_bitfield_t* b )
{
size_t ret = 0;
const uint8_t *it, *end;
static const int trueBitCount[512] = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8,
4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8,5,6,6,7,6,7,7,8,6,7,7,8,7,8,8,9
};
for( it=b->bits, end=it+b->len; it!=end; ++it )
ret += trueBitCount[*it];
return ret;
}

View File

@ -49,8 +49,8 @@ int tr_mkdir( char * path );
***********************************************************************
* 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 );
#define tr_strcasecmp( ff, ss ) ( tr_strncasecmp( (ff), (ss), ULONG_MAX ) )
int tr_strncasecmp( const char * first, const char * second, size_t len );
/***********************************************************************
* tr_sprintf
@ -103,70 +103,6 @@ static inline void tr_wait( uint64_t delay )
#endif
}
struct tr_bitfield_s
{
uint8_t * bits;
int len;
};
/* note that the argument is how many bits are needed, not bytes */
static inline tr_bitfield_t * tr_bitfieldNew( int bitcount )
{
tr_bitfield_t * ret;
ret = calloc( 1, sizeof *ret );
if( NULL == ret )
{
return NULL;
}
ret->len = ( bitcount + 7 ) / 8;
ret->bits = calloc( ret->len, 1 );
if( NULL == ret->bits )
{
free( ret );
return NULL;
}
return ret;
}
static inline void tr_bitfieldFree( tr_bitfield_t * bitfield )
{
if( bitfield )
{
free( bitfield->bits );
free( bitfield );
}
}
static inline void tr_bitfieldClear( tr_bitfield_t * bitfield )
{
memset( bitfield->bits, 0, bitfield->len );
}
/***********************************************************************
* tr_bitfieldHas
**********************************************************************/
static inline int tr_bitfieldHas( tr_bitfield_t * bitfield, int piece )
{
assert( piece / 8 < bitfield->len );
return ( bitfield->bits[ piece / 8 ] & ( 1 << ( 7 - ( piece % 8 ) ) ) );
}
/***********************************************************************
* tr_bitfieldAdd
**********************************************************************/
static inline void tr_bitfieldAdd( tr_bitfield_t * bitfield, int piece )
{
assert( piece / 8 < bitfield->len );
bitfield->bits[ piece / 8 ] |= ( 1 << ( 7 - ( piece % 8 ) ) );
}
static inline void tr_bitfieldRem( tr_bitfield_t * bitfield, int piece )
{
assert( piece / 8 < bitfield->len );
bitfield->bits[ piece / 8 ] &= ~( 1 << ( 7 - ( piece % 8 ) ) );
}
#define tr_blockPiece(a) _tr_blockPiece(tor,a)
static inline int _tr_blockPiece( const tr_torrent_t * tor, int block )
@ -237,4 +173,43 @@ static inline int _tr_block( const tr_torrent_t * tor, int index, int begin )
begin / tor->blockSize;
}
/***
****
***/
char* tr_strdup( const char * pch );
void* tr_malloc( size_t );
void tr_free( void* );
/***
****
***/
struct tr_bitfield_s
{
uint8_t * bits;
size_t len;
};
tr_bitfield_t* tr_bitfieldNew( size_t bitcount );
tr_bitfield_t* tr_bitfieldDup( const tr_bitfield_t* );
void tr_bitfieldFree( tr_bitfield_t*);
void tr_bitfieldClear( tr_bitfield_t* );
void tr_bitfieldAdd( tr_bitfield_t*, size_t bit );
void tr_bitfieldRem( tr_bitfield_t*, size_t bit );
void tr_bitfieldAddRange( tr_bitfield_t *, size_t first, size_t last );
void tr_bitfieldRemRange ( tr_bitfield_t*, size_t first, size_t last );
int tr_bitfieldIsEmpty( const tr_bitfield_t* );
int tr_bitfieldHas( const tr_bitfield_t *, size_t bit );
size_t tr_bitfieldCountTrueBits( const tr_bitfield_t* );
tr_bitfield_t* tr_bitfieldNegate( tr_bitfield_t* );
tr_bitfield_t* tr_bitfieldAnd( tr_bitfield_t*, const tr_bitfield_t* );
#endif

View File

@ -32,6 +32,7 @@
@interface CTGradient (ProgressBar)
+ (CTGradient *)progressWhiteGradient;
+ (CTGradient *)progressGrayGradient;
+ (CTGradient *)progressLightGrayGradient;
+ (CTGradient *)progressBlueGradient;
+ (CTGradient *)progressGreenGradient;
+ (CTGradient *)progressLightGreenGradient;

View File

@ -104,17 +104,17 @@
color1.position = 0;
CTGradientElement color2;
color2.red = color2.green = color2.blue = 0.8;
color2.red = color2.green = color2.blue = 0.83;
color2.alpha = 1.00;
color2.position = 0.5;
color2.position = 11.5/23;
CTGradientElement color3;
color3.red = color3.green = color3.blue = 0.95;
color3.alpha = 1.00;
color3.position = 0.5;
color3.position = 11.5/23;
CTGradientElement color4;
color4.red = color4.green = color4.blue = 0.95;
color4.red = color4.green = color4.blue = 0.92;
color4.alpha = 1.00;
color4.position = 1;
@ -158,6 +158,38 @@
return [newInstance autorelease];
}
+ (CTGradient *)progressLightGrayGradient
{
CTGradient *newInstance = [[[self class] alloc] init];
CTGradientElement color1;
color1.red = color1.green = color1.blue = 0.87;
color1.alpha = 1.00;
color1.position = 0;
CTGradientElement color2;
color2.red = color2.green = color2.blue = 0.754;
color2.alpha = 1.00;
color2.position = 0.5;
CTGradientElement color3;
color3.red = color3.green = color3.blue = 0.87;
color3.alpha = 1.00;
color3.position = 0.5;
CTGradientElement color4;
color4.red = color4.green = color4.blue = 0.87;
color4.alpha = 1.00;
color4.position = 1;
[newInstance addElement:&color1];
[newInstance addElement:&color2];
[newInstance addElement:&color3];
[newInstance addElement:&color4];
return [newInstance autorelease];
}
+ (CTGradient *)progressBlueGradient
{
CTGradient *newInstance = [[[self class] alloc] init];

View File

@ -104,7 +104,9 @@
}
- (void) openFiles: (NSArray *) filenames;
- (void) openFiles: (NSArray *) filenames ignoreDownloadFolder: (BOOL) ignore forceDeleteTorrent: (BOOL) delete;
- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
forceDeleteTorrent: (BOOL) delete;
- (void) openCreatedFile: (NSNotification *) notification;
- (void) openFilesWithDict: (NSDictionary *) dictionary;
- (void) openFilesAsk: (NSMutableArray *) files forceDeleteTorrent: (BOOL) delete;
- (void) openFilesAskWithDict: (NSDictionary *) dictionary;
@ -116,6 +118,8 @@
- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo;
- (void) createFile: (id) sender;
- (void) resumeSelectedTorrents: (id) sender;
- (void) resumeAllTorrents: (id) sender;
- (void) resumeTorrents: (NSArray *) torrents;

View File

@ -28,6 +28,7 @@
#import "Torrent.h"
#import "TorrentCell.h"
#import "TorrentTableView.h"
#import "CreatorWindowController.h"
#import "StringAdditions.h"
#import "UKKQueue.h"
#import "ActionMenuSpeedToDisplayLimitTransformer.h"
@ -38,6 +39,7 @@
#import <Sparkle/Sparkle.h>
#define TOOLBAR_CREATE @"Toolbar Create"
#define TOOLBAR_OPEN @"Toolbar Open"
#define TOOLBAR_REMOVE @"Toolbar Remove"
#define TOOLBAR_INFO @"Toolbar Info"
@ -170,7 +172,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow" handle: fLib];
fBadger = [[Badger alloc] initWithLib: fLib];
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib];
fIPCController = [[IPCController alloc] init];
[fIPCController setDelegate: self];
@ -195,7 +196,8 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[fTorrents release];
[fDisplayedTorrents release];
[fBadger release];
[fOverlayWindow release];
if (fOverlayWindow)
[fOverlayWindow release];
[fIPCController release];
[fAutoImportedNames release];
@ -212,7 +214,6 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
[fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
[fWindow addChildWindow: fOverlayWindow ordered: NSWindowAbove];
fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
[fToolbar setDelegate: self];
@ -414,6 +415,10 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
//change that just impacts the dock badge
[nc addObserver: self selector: @selector(updateDockBadge:)
name: @"DockBadgeChange" object: nil];
//open newly created torrent file
[nc addObserver: self selector: @selector(openCreatedFile:)
name: @"OpenCreatedTorrentFile" object: nil];
//timer to update the interface every second
[self updateUI];
@ -632,7 +637,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
{
NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
[self openFiles: [NSArray arrayWithObject: path] ignoreDownloadFolder:
[self openFiles: [NSArray arrayWithObject: path] forcePath: nil ignoreDownloadFolder:
![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] forceDeleteTorrent: YES];
[fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
@ -644,19 +649,20 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
{
[self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
[self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO forceDeleteTorrent: NO];
}
- (void) openFiles: (NSArray *) filenames ignoreDownloadFolder: (BOOL) ignore forceDeleteTorrent: (BOOL) delete
- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
forceDeleteTorrent: (BOOL) delete
{
NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
if (ignore || [downloadChoice isEqualToString: @"Ask"])
if (ignore || (!path && [downloadChoice isEqualToString: @"Ask"]))
{
[self openFilesAsk: [filenames mutableCopy] forceDeleteTorrent: delete];
return;
}
if ([fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
if (!path && [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
&& access([[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
@ -680,7 +686,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
didEndSelector: @selector(incompleteChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
return;
}
if ([downloadChoice isEqualToString: @"Constant"]
if (!path && [downloadChoice isEqualToString: @"Constant"]
&& access([[[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
@ -711,13 +717,17 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
{
if (!(torrent = [[Torrent alloc] initWithPath: torrentPath forceDeleteTorrent: delete lib: fLib]))
continue;
//add it to the "File > Open Recent" menu
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
NSString * folder = [downloadChoice isEqualToString: @"Constant"]
? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
: [torrentPath stringByDeletingLastPathComponent];
NSString * folder;
if (path)
folder = path;
else if ([downloadChoice isEqualToString: @"Constant"])
folder = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
else
folder = [torrentPath stringByDeletingLastPathComponent];
[torrent setDownloadFolder: folder];
[torrent update];
@ -729,6 +739,14 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
[self updateTorrentsInQueue];
}
- (void) openCreatedFile: (NSNotification *) notification
{
NSDictionary * dict = [notification userInfo];
[self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] forcePath: [dict objectForKey: @"Path"]
ignoreDownloadFolder: NO forceDeleteTorrent: NO];
[dict release];
}
- (void) incompleteChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
{
if (code == NSOKButton)
@ -752,7 +770,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) openFilesWithDict: (NSDictionary *) dictionary
{
[self openFiles: [dictionary objectForKey: @"Filenames"]
[self openFiles: [dictionary objectForKey: @"Filenames"] forcePath: nil
ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue]
forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
@ -842,7 +860,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) openFiles: (NSArray *) filenames
{
[self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
[self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO forceDeleteTorrent: NO];
}
- (void) openShowSheet: (id) sender
@ -870,7 +888,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) openFromSheet: (NSDictionary *) dictionary
{
[self openFiles: [dictionary objectForKey: @"Files"]
[self openFiles: [dictionary objectForKey: @"Files"] forcePath: nil
ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] forceDeleteTorrent: NO];
[dictionary release];
@ -929,6 +947,11 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
}
}
- (void) createFile: (id) sender
{
[CreatorWindowController createTorrentFile: fLib];
}
- (void) resumeSelectedTorrents: (id) sender
{
[self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
@ -1635,7 +1658,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
NSString * filterType = [fDefaults stringForKey: @"Filter"];
BOOL filtering = ![filterType isEqualToString: @"None"];
int downloading = 0, seeding = 0, paused = 0, all = 0;
int downloading = 0, seeding = 0, paused = 0;
BOOL isDownloading = [filterType isEqualToString: @"Download"],
isSeeding = [filterType isEqualToString: @"Seed"],
isPaused = [filterType isEqualToString: @"Pause"];
@ -2121,11 +2144,12 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
NSPasteboard * pasteboard = [info draggingPasteboard];
if ([[pasteboard types] containsObject: NSFilenamesPboardType])
{
//check if any files can be added
//check if any torrent files can be added
NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
NSEnumerator * enumerator = [files objectEnumerator];
NSString * file;
tr_torrent_t * tempTor;
BOOL torrent = NO;
while ((file = [enumerator nextObject]))
{
int error;
@ -2133,14 +2157,33 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
{
tr_torrentClose(tempTor);
[fOverlayWindow setFiles: files];
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setTorrents: files];
return NSDragOperationCopy;
}
else
{
if (error == TR_EUNSUPPORTED || error == TR_EDUPLICATE)
torrent = YES;
}
}
//create a torrent file if a single file
if (!torrent && [files count] == 1)
{
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
return NSDragOperationCopy;
}
}
else if ([[pasteboard types] containsObject: NSURLPboardType])
{
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
return NSDragOperationCopy;
@ -2152,19 +2195,24 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (void) draggingExited: (id <NSDraggingInfo>) info
{
[fOverlayWindow fadeOut];
if (fOverlayWindow)
[fOverlayWindow fadeOut];
}
- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
{
[fOverlayWindow fadeOut];
if (fOverlayWindow)
[fOverlayWindow fadeOut];
NSPasteboard * pasteboard = [info draggingPasteboard];
if ([[pasteboard types] containsObject: NSFilenamesPboardType])
{
BOOL torrent = NO, accept = YES;
//create an array of files that can be opened
NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
NSEnumerator * enumerator = [files objectEnumerator];
NSString * file;
tr_torrent_t * tempTor;
while ((file = [enumerator nextObject]))
@ -2174,13 +2222,28 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
{
tr_torrentClose(tempTor);
[filesToOpen addObject: file];
torrent = YES;
}
else
{
if (error == TR_EUNSUPPORTED || error == TR_EDUPLICATE)
torrent = YES;
}
}
[self application: NSApp openFiles: filesToOpen];
if ([filesToOpen count] > 0)
[self application: NSApp openFiles: filesToOpen];
else
{
if (!torrent && [files count] == 1)
[CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
else
accept = NO;
}
[filesToOpen release];
return YES;
return accept;
}
else if ([[pasteboard types] containsObject: NSURLPboardType])
{
@ -2397,7 +2460,17 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
{
NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
if ([ident isEqualToString: TOOLBAR_OPEN])
if ([ident isEqualToString: TOOLBAR_CREATE])
{
[item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
[item setImage: [NSImage imageNamed: @"Create.png"]];
[item setTarget: self];
[item setAction: @selector(createFile:)];
[item setAutovalidates: NO];
}
else if ([ident isEqualToString: TOOLBAR_OPEN])
{
[item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
@ -2484,7 +2557,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
{
return [NSArray arrayWithObjects:
TOOLBAR_OPEN, TOOLBAR_REMOVE,
TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
NSToolbarSeparatorItemIdentifier,
@ -2496,7 +2569,7 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
{
return [NSArray arrayWithObjects:
TOOLBAR_OPEN, TOOLBAR_REMOVE,
TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
NSToolbarSeparatorItemIdentifier,
TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
NSToolbarFlexibleSpaceItemIdentifier,
@ -2850,13 +2923,13 @@ static void sleepCallBack(void * controller, io_service_t y, natural_t messageTy
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
while ((torrent = [enumerator nextObject]))
if ([torrent isActive])
{
if ([torrent allDownloaded])
seeding++;
else
downloading++;
}
{
if ([torrent isSeeding])
seeding++;
else if ([torrent isActive])
downloading++;
else;
}
NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
* downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];

View File

@ -0,0 +1,57 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2007 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.
*****************************************************************************/
#import <Cocoa/Cocoa.h>
#import "transmission.h"
#import "makemeta.h"
@interface CreatorWindowController : NSWindowController
{
IBOutlet NSImageView * fIcon;
IBOutlet NSTextField * fNameField, * fStatusField, * fPiecesField, * fTrackerField, * fLocationField;
IBOutlet NSTextView * fCommentView;
IBOutlet NSButton * fPrivateCheck, * fOpenCheck;
IBOutlet NSView * fProgressView;
IBOutlet NSProgressIndicator * fProgressIndicator;
tr_metainfo_builder_t * fInfo;
NSString * fPath, * fLocation;
BOOL fOpenTorrent;
NSTimer * fTimer;
BOOL fStarted;
}
+ (void) createTorrentFile: (tr_handle_t *) handle;
+ (void) createTorrentFile: (tr_handle_t *) handle forFile: (NSString *) file;
- (id) initWithWindowNibName: (NSString *) name handle: (tr_handle_t *) handle path: (NSString *) path;
- (void) setLocation: (id) sender;
- (void) create: (id) sender;
- (void) cancelCreateWindow: (id) sender;
- (void) cancelCreateProgress: (id) sender;
@end

View File

@ -0,0 +1,340 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2007 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.
*****************************************************************************/
#import "CreatorWindowController.h"
#import "StringAdditions.h"
#define DEFAULT_SAVE_LOCATION @"~/Desktop/"
@interface CreatorWindowController (Private)
+ (NSString *) chooseFile;
- (void) locationSheetClosed: (NSSavePanel *) openPanel returnCode: (int) code contextInfo: (void *) info;
- (void) checkProgress;
- (void) failureSheetClosed: (NSAlert *) alert returnCode: (int) code contextInfo: (void *) info;
@end
@implementation CreatorWindowController
+ (void) createTorrentFile: (tr_handle_t *) handle
{
//get file/folder for torrent
NSString * path;
if (!(path = [CreatorWindowController chooseFile]))
return;
CreatorWindowController * creator = [[self alloc] initWithWindowNibName: @"Creator" handle: handle path: path];
[creator showWindow: nil];
}
+ (void) createTorrentFile: (tr_handle_t *) handle forFile: (NSString *) file
{
CreatorWindowController * creator = [[self alloc] initWithWindowNibName: @"Creator" handle: handle path: file];
[creator showWindow: nil];
}
- (id) initWithWindowNibName: (NSString *) name handle: (tr_handle_t *) handle path: (NSString *) path
{
if ((self = [super initWithWindowNibName: name]))
{
fStarted = NO;
fPath = [path retain];
fInfo = tr_metaInfoBuilderCreate(handle, [fPath UTF8String]);
if (fInfo->fileCount == 0)
{
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no files -> button")];
[alert setMessageText: NSLocalizedString(@"This folder contains no files.",
"Create torrent -> no files -> title")];
[alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
"Create torrent -> no files -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
[alert runModal];
[self release];
return nil;
}
}
return self;
}
- (void) awakeFromNib
{
NSString * name = [fPath lastPathComponent];
[[self window] setTitle: name];
[fNameField setStringValue: name];
[fNameField setToolTip: fPath];
BOOL multifile = !fInfo->isSingleFile;
#warning fix when resizing window
[fIcon setImage: [[NSWorkspace sharedWorkspace] iconForFileType: multifile
? NSFileTypeForHFSTypeCode('fldr') : [fPath pathExtension]]];
NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
if (multifile)
{
NSString * fileString;
int count = fInfo->fileCount;
if (count != 1)
fileString = [NSString stringWithFormat: NSLocalizedString(@"%d Files, ", "Create torrent -> info"), count];
else
fileString = NSLocalizedString(@"1 File, ", "Create torrent -> info");
statusString = [fileString stringByAppendingString: statusString];
}
[fStatusField setStringValue: statusString];
NSString * piecesCountString;
int piecesCount = fInfo->pieceCount;
if (piecesCount == 1)
piecesCountString = NSLocalizedString(@"1 piece", "Create torrent -> info");
else
piecesCountString = [NSString stringWithFormat: NSLocalizedString(@"%d pieces", "Create torrent -> info"),
piecesCount];
[fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@, %@ each", "Create torrent -> info"),
piecesCountString, [NSString stringForFileSize: fInfo->pieceSize]]];
fLocation = [[[DEFAULT_SAVE_LOCATION stringByAppendingPathComponent: [name stringByAppendingPathExtension: @"torrent"]]
stringByExpandingTildeInPath] retain];
[fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
[fLocationField setToolTip: fLocation];
}
- (void) dealloc
{
[fPath release];
if (fLocation)
[fLocation release];
if (fInfo)
tr_metaInfoBuilderFree(fInfo);
if (fTimer)
[fTimer invalidate];
[super dealloc];
}
- (void) setLocation: (id) sender
{
NSSavePanel * panel = [NSSavePanel savePanel];
[panel setPrompt: @"Select"];
[panel setRequiredFileType: @"torrent"];
[panel setCanSelectHiddenExtension: YES];
[panel beginSheetForDirectory: nil file: [fLocation lastPathComponent] modalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(locationSheetClosed:returnCode:contextInfo:) contextInfo: nil];
}
- (void) create: (id) sender
{
//parse tracker string
NSString * trackerString = [fTrackerField stringValue];
if ([trackerString rangeOfString: @"://"].location != NSNotFound)
{
if (![trackerString hasPrefix: @"http://"])
{
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> http warning -> button")];
[alert setMessageText: NSLocalizedString(@"The tracker address must begin with \"http://\".",
"Create torrent -> http warning -> title")];
[alert setInformativeText: NSLocalizedString(@"Change the tracker address to create the torrent.",
"Create torrent -> http warning -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
return;
}
}
else
trackerString = [@"http://" stringByAppendingString: trackerString];
//don't allow blank addresses
if ([trackerString length] <= 7)
{
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no url warning -> button")];
[alert setMessageText: NSLocalizedString(@"The tracker address cannot be blank.",
"Create torrent -> no url warning -> title")];
[alert setInformativeText: NSLocalizedString(@"Change the tracker address to create the torrent.",
"Create torrent -> no url warning -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
return;
}
//check if a file with the same name and location already exists
if ([[NSFileManager defaultManager] fileExistsAtPath: fLocation])
{
NSArray * pathComponents = [fLocation pathComponents];
int count = [pathComponents count];
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
[alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
"Create torrent -> file already exists warning -> title")];
[alert setInformativeText: [NSString stringWithFormat:
NSLocalizedString(@"A file with the name \"%@\" already exists in the directory \"%@\". "
"Choose a new name or directory to create the torrent.",
"Create torrent -> file already exists warning -> warning"),
[pathComponents objectAtIndex: count-1], [pathComponents objectAtIndex: count-2]]];
[alert setAlertStyle: NSWarningAlertStyle];
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
return;
}
fOpenTorrent = [fOpenCheck state] == NSOnState;
tr_makeMetaInfo(fInfo, [fLocation UTF8String], [trackerString UTF8String], [[fCommentView string] UTF8String],
[fPrivateCheck state] == NSOnState);
fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress)
userInfo: nil repeats: YES];
}
- (void) cancelCreateWindow: (id) sender
{
[[self window] close];
}
- (void) windowWillClose: (NSNotification *) notification
{
[self release];
}
- (void) cancelCreateProgress: (id) sender
{
fInfo->abortFlag = 1;
[fTimer fire];
}
@end
@implementation CreatorWindowController (Private)
+ (NSString *) chooseFile
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
[panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
[panel setAllowsMultipleSelection: NO];
[panel setCanChooseFiles: YES];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: NO];
[panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
BOOL success = [panel runModal] == NSOKButton;
return success ? [[panel filenames] objectAtIndex: 0] : nil;
}
- (void) locationSheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (void *) info
{
if (code == NSOKButton)
{
[fLocation release];
fLocation = [[panel filename] retain];
[fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
[fLocationField setToolTip: fLocation];
}
}
- (void) checkProgress
{
if (fInfo->isDone)
{
[fTimer invalidate];
fTimer = nil;
if (fInfo->failed)
{
if (!fInfo->abortFlag)
{
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
"Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
[alert setInformativeText: NSLocalizedString(@"There was an error parsing the data file. "
"The torrent file was not created.", "Create torrent -> failed -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
[alert beginSheetModalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
return;
}
}
else
{
if (fOpenTorrent)
{
NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: fLocation, @"File",
[fPath stringByDeletingLastPathComponent], @"Path", nil];
[[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
}
}
[[self window] close];
}
else
{
[fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
if (!fStarted)
{
NSWindow * window = [self window];
NSRect windowRect = [window frame];
float difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
windowRect.origin.y -= difference;
windowRect.size.height += difference;
//don't allow vertical resizing
float height = windowRect.size.height;
[window setMinSize: NSMakeSize([window minSize].width, height)];
[window setMaxSize: NSMakeSize([window maxSize].width, height)];
[window setContentView: fProgressView];
[window setFrame: windowRect display: YES animate: YES];
[fProgressView setHidden: NO];
fStarted = YES;
}
}
}
- (void) failureSheetClosed: (NSAlert *) alert returnCode: (int) code contextInfo: (void *) info
{
[[alert window] orderOut: nil];
[[self window] close];
}
@end

View File

@ -1,7 +1,6 @@
{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\vieww9000\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural
\f0\fs24 \cf0 Transmission is written and maintained by:\
@ -21,6 +20,10 @@ Mitchell Livingston <livings124@gmail.com>\
\
Bryan Varner <bryan@varnernet.com>\
+ BeOS interface\
\
Charles Kerr <charles@rebelbase.com>\
+ Back-end\
+ GTK+ interface\
\
\
Translators:\

View File

@ -29,11 +29,13 @@
{
tr_handle_t * fLib;
//NSTimer * fFadeInTimer, * fFadeOutTimer;
NSViewAnimation * fFadeInAnimation, * fFadeOutAnimation;
}
- (void) setFiles: (NSArray *) files;
- (id) initWithLib: (tr_handle_t *) lib forWindow: (NSWindow *) window;
- (void) setTorrents: (NSArray *) files;
- (void) setFile: (NSString *) file;
- (void) setURL: (NSString *) url;
- (void) fadeOut;

View File

@ -28,7 +28,7 @@
@implementation DragOverlayWindow
- (id) initWithLib: (tr_handle_t *) lib
- (id) initWithLib: (tr_handle_t *) lib forWindow: (NSWindow *) window
{
if (self = ([super initWithContentRect: NSMakeRect(0, 0, 1.0, 1.0) styleMask: NSBorderlessWindowMask
backing: NSBackingStoreBuffered defer: NO]))
@ -57,6 +57,8 @@
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil]]];
[fFadeOutAnimation setDuration: 0.5];
[fFadeOutAnimation setAnimationBlockingMode: NSAnimationNonblockingThreaded];
[window addChildWindow: self ordered: NSWindowAbove];
}
return self;
}
@ -69,7 +71,7 @@
[super dealloc];
}
- (void) setFiles: (NSArray *) files
- (void) setTorrents: (NSArray *) files
{
uint64_t size = 0;
int count = 0;
@ -114,9 +116,9 @@
{
NSString * fileString;
if (fileCount == 1)
fileString = NSLocalizedString(@"1 File, ", "Drag overlay -> drag files");
fileString = NSLocalizedString(@"1 File, ", "Drag overlay -> torrents");
else
fileString= [NSString stringWithFormat: NSLocalizedString(@"%d Files, ", "Drag overlay -> drag files"), fileCount];
fileString= [NSString stringWithFormat: NSLocalizedString(@"%d Files, ", "Drag overlay -> torrents"), fileCount];
secondString = [fileString stringByAppendingString: secondString];
}
@ -124,7 +126,7 @@
icon = [[NSWorkspace sharedWorkspace] iconForFileType: folder ? NSFileTypeForHFSTypeCode('fldr') : [name pathExtension]];
else
{
name = [NSString stringWithFormat: NSLocalizedString(@"%d Torrent Files", "Drag overlay -> drag files"), count];
name = [NSString stringWithFormat: NSLocalizedString(@"%d Torrent Files", "Drag overlay -> torrents"), count];
secondString = [secondString stringByAppendingString: @" Total"];
}
@ -140,6 +142,23 @@
[fFadeInAnimation startAnimation];
}
- (void) setFile: (NSString *) file
{
[[self contentView] setOverlay: [NSImage imageNamed: @"Create.png"]
mainLine: NSLocalizedString(@"Create a Torrent File", "Drag overlay -> file") subLine: file];
//stop other animation and set to same progress
if ([fFadeOutAnimation isAnimating])
{
[fFadeOutAnimation stopAnimation];
[fFadeInAnimation setCurrentProgress: 1.0 - [fFadeOutAnimation currentProgress]];
}
[self setFrame: [[self parentWindow] frame] display: YES];
[fFadeInAnimation startAnimation];
}
- (void) setURL: (NSString *) url
{
[[self contentView] setOverlay: [NSImage imageNamed: @"Globe.tiff"]

View File

@ -0,0 +1,30 @@
{
IBClasses = (
{
ACTIONS = {
cancelCreateProgress = id;
cancelCreateWindow = id;
create = id;
setLocation = id;
};
CLASS = CreatorWindowController;
LANGUAGE = ObjC;
OUTLETS = {
fCommentView = NSTextView;
fIcon = NSImageView;
fLocationField = NSTextField;
fNameField = NSTextField;
fOpenCheck = NSButton;
fPiecesField = NSTextField;
fPrivateCheck = NSButton;
fProgressIndicator = NSProgressIndicator;
fProgressView = NSView;
fStatusField = NSTextField;
fTrackerField = NSTextField;
};
SUPERCLASS = NSWindowController;
},
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
);
IBVersion = 1;
}

View File

@ -0,0 +1,22 @@
<?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>52 61 356 240 0 0 1152 842 </string>
<key>IBEditorPositions</key>
<dict>
<key>56</key>
<string>392 515 368 126 0 0 1152 842 </string>
</dict>
<key>IBFramework Version</key>
<string>446.1</string>
<key>IBOpenObjects</key>
<array>
<integer>5</integer>
<integer>56</integer>
</array>
<key>IBSystem Version</key>
<string>8P135</string>
</dict>
</plist>

Binary file not shown.

View File

@ -7,8 +7,10 @@
revealDataFile = id;
revealFile = id;
revealTorrentFile = id;
setCheck = id;
setLimitSetting = id;
setPex = id;
setPriority = id;
setRatioLimit = id;
setRatioSetting = id;
setSpeedLimit = id;
@ -32,8 +34,13 @@
fDownloadedValidField = NSTextField;
fDownloadingFromField = NSTextField;
fErrorMessageView = NSTextView;
fFileCheckItem = NSMenuItem;
fFileOutline = NSOutlineView;
fFilePriorityHigh = NSMenuItem;
fFilePriorityLow = NSMenuItem;
fFilePriorityNormal = NSMenuItem;
fFileTableStatusField = NSTextField;
fFileUncheckItem = NSMenuItem;
fHashField = NSTextField;
fImageView = NSImageView;
fLeechersField = NSTextField;

View File

@ -7,7 +7,7 @@
<key>IBEditorPositions</key>
<dict>
<key>549</key>
<string>364 417 144 49 0 0 1152 842 </string>
<string>565 283 144 118 0 0 1152 842 </string>
</dict>
<key>IBFramework Version</key>
<string>446.1</string>

View File

@ -7,6 +7,7 @@
applyFilter = id;
applySpeedLimit = id;
copyTorrentFiles = id;
createFile = id;
doNothing = id;
linkForums = id;
linkHomepage = id;
@ -57,7 +58,7 @@
fDownloadLimitItem = NSMenuItem;
fDownloadMenu = NSMenu;
fDownloadNoLimitItem = NSMenuItem;
fFilterBar = ImageBackgroundView;
fFilterBar = FilterBarView;
fNameSortActionItem = NSMenuItem;
fNameSortItem = NSMenuItem;
fNextFilterItem = NSMenuItem;

View File

@ -32,7 +32,7 @@
<key>IBOpenObjects</key>
<array>
<integer>21</integer>
<integer>1603</integer>
<integer>29</integer>
</array>
<key>IBSystem Version</key>
<string>8P135</string>

Binary file not shown.

View File

@ -26,6 +26,9 @@
@interface FileBrowserCell : NSBrowserCell
{
float fPercent;
}
- (void) setProgress: (float) progress;
@end

View File

@ -36,11 +36,19 @@
- (void) setImage: (NSImage *) image
{
if (!image)
image = [[[[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')] copy] autorelease];
[image setFlipped: YES];
[image setScalesWhenResized: YES];
[super setImage: image];
}
- (void) setProgress: (float) progress
{
fPercent = progress * 100.0;
}
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) controlView
{
NSMutableDictionary * item;
@ -85,8 +93,7 @@
paragraphStyle, NSParagraphStyleAttributeName, nil];
NSString * statusString = [NSString stringWithFormat: NSLocalizedString(@"%.2f%% of %@",
"Inspector -> Files tab -> file status string"),
100.0 * [[item objectForKey: @"Progress"] floatValue],
"Inspector -> Files tab -> file status string"), fPercent,
[NSString stringForFileSize: [[item objectForKey: @"Size"] unsignedLongLongValue]]];
NSRect statusTextRect = nameTextRect;

View File

@ -26,6 +26,7 @@
@interface FileOutlineView : NSOutlineView
{
NSColor * fNormalColor, * fHighPriorityColor, * fLowPriorityColor;
}
@end

View File

@ -24,6 +24,7 @@
#import "FileOutlineView.h"
#import "FileBrowserCell.h"
#import "Torrent.h"
@implementation FileOutlineView
@ -34,6 +35,18 @@
[self setAutoresizesOutlineColumn: NO];
[self setIndentationPerLevel: 14.0];
fNormalColor = [self backgroundColor];
fHighPriorityColor = [[NSColor colorWithCalibratedRed: 1.0 green: 208.0/255.0 blue: 208.0/255.0 alpha: 1.0] retain];
fLowPriorityColor = [[NSColor colorWithCalibratedRed: 1.0 green: 1.0 blue: 224.0/255.0 alpha: 1.0] retain];
}
- (void) dealloc
{
[fHighPriorityColor release];
[fLowPriorityColor release];
[super dealloc];
}
- (void) mouseDown: (NSEvent *) event
@ -42,13 +55,13 @@
[super mouseDown: event];
}
- (NSMenu *) menuForEvent: (NSEvent *) e
- (NSMenu *) menuForEvent: (NSEvent *) event
{
int row = [self rowAtPoint: [self convertPoint: [e locationInWindow] fromView: nil]];
int row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
if (row >= 0)
{
if ([self itemAtRow: row] && ![self isRowSelected: row])
if (![self isRowSelected: row])
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
}
else
@ -57,4 +70,65 @@
return [self menu];
}
- (void) drawRow: (int) row clipRect: (NSRect) clipRect
{
if (![self isRowSelected: row])
{
NSDictionary * item = [self itemAtRow: row];
if ([[item objectForKey: @"IsFolder"] boolValue])
[fNormalColor set];
else
{
int priority = [[item objectForKey: @"Priority"] intValue];
if (priority == PRIORITY_HIGH)
[fHighPriorityColor set];
else if (priority == PRIORITY_LOW)
[fLowPriorityColor set];
else
[fNormalColor set];
}
NSRect rect = [self rectOfRow: row];
rect.size.height -= 1.0;
NSRectFill(rect);
}
[super drawRow: row clipRect: clipRect];
}
- (void) drawRect: (NSRect) r
{
[super drawRect: r];
NSDictionary * item;
int i, priority;
for (i = 0; i < [self numberOfRows]; i++)
{
if ([self isRowSelected: i])
{
item = [self itemAtRow: i];
if (![[item objectForKey: @"IsFolder"] boolValue])
{
priority = [[item objectForKey: @"Priority"] intValue];
if (priority == PRIORITY_HIGH)
[fHighPriorityColor set];
else if (priority == PRIORITY_LOW)
[fLowPriorityColor set];
else
continue;
NSRect rect = [self rectOfRow: i];
float width = 14.0;
rect.origin.y += (rect.size.height - width) * 0.5;
rect.origin.x += 3.0;
rect.size.width = width;
rect.size.height = width;
[[NSBezierPath bezierPathWithOvalInRect: rect] fill];
}
}
}
}
@end

View File

@ -470,8 +470,7 @@ msg_default ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
if( IPC_MSG_INFO == respid )
res = ipc_addinfo( pkinf, [tor torrentID], [tor torrentInfo], types );
else
res = ipc_addstat( pkinf, [tor torrentID], [tor torrentInfo],
[tor torrentStat], types );
res = ipc_addstat( pkinf, [tor torrentID], [tor torrentStat], types );
if( 0 > res )
{
tr_bencFree( &packet );

BIN
macosx/Images/Create.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -30,7 +30,7 @@
@interface InfoWindowController : NSWindowController
{
NSArray * fTorrents, * fPeers, * fFiles;
NSImage * fAppIcon, * fDotGreen, * fDotRed, * fFolderIcon;
NSImage * fAppIcon, * fDotGreen, * fDotRed;
IBOutlet NSTabView * fTabView;
@ -54,6 +54,8 @@
IBOutlet NSOutlineView * fFileOutline;
IBOutlet NSTextField * fFileTableStatusField;
IBOutlet NSMenuItem * fFileCheckItem, * fFileUncheckItem,
* fFilePriorityNormal, * fFilePriorityHigh, * fFilePriorityLow;
IBOutlet NSPopUpButton * fRatioPopUp, * fUploadLimitPopUp, * fDownloadLimitPopUp;
IBOutlet NSTextField * fUploadLimitField, * fDownloadLimitField, * fRatioLimitField,
@ -73,6 +75,9 @@
- (void) revealDataFile: (id) sender;
- (void) revealFile: (id) sender;
- (void) setCheck: (id) sender;
- (void) setPriority: (id) sender;
- (void) setLimitSetting: (id) sender;
- (void) setSpeedLimit: (id) sender;

View File

@ -23,6 +23,7 @@
*****************************************************************************/
#import "InfoWindowController.h"
#import "FileBrowserCell.h"
#import "StringAdditions.h"
#define MIN_WINDOW_WIDTH 300
@ -74,8 +75,6 @@
fAppIcon = [NSImage imageNamed: @"NSApplicationIcon"];
fDotGreen = [NSImage imageNamed: @"GreenDot.tiff"];
fDotRed = [NSImage imageNamed: @"RedDot.tiff"];
fFolderIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('fldr')] copy];
}
return self;
}
@ -106,6 +105,15 @@
//set file table
[fFileOutline setDoubleAction: @selector(revealFile:)];
//set file outline
NSSegmentedCell * priorityCell = [[fFileOutline tableColumnWithIdentifier: @"Priority"] dataCell];
int i;
for (i = 0; i < [priorityCell segmentCount]; i++)
{
[priorityCell setLabel: @"" forSegment: i];
[priorityCell setWidth: 6.0 forSegment: i];
}
//set blank inspector
[self updateInfoForTorrents: [NSArray array]];
}
@ -119,7 +127,6 @@
if (fFiles)
[fFiles release];
[fFolderIcon release];
[super dealloc];
}
@ -428,10 +435,7 @@
- (void) updateInfoFiles
{
if ([fTorrents count] != 1)
return;
if ([[fTorrents objectAtIndex: 0] updateFileProgress])
if ([fTorrents count] == 1)
[fFileOutline reloadData];
}
@ -593,7 +597,60 @@
if (action == @selector(revealFile:))
return [fFileOutline numberOfSelectedRows] > 0 &&
[[[fTabView selectedTabViewItem] identifier] isEqualToString: TAB_FILES_IDENT];
if (action == @selector(setCheck:))
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSDictionary * item;
NSIndexSet * indexSet = [fFileOutline selectedRowIndexes], * itemIndexes;
NSMutableIndexSet * usedIndexes = [NSMutableIndexSet indexSet];
int i, index, state = (menuItem == fFileCheckItem) ? NSOnState : NSOffState;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
itemIndexes = [[fFileOutline itemAtRow: i] objectForKey: @"Indexes"];
if (![usedIndexes containsIndexes: itemIndexes])
{
if ([torrent checkForFiles: itemIndexes] != state && [torrent canChangeDownloadCheckForFiles: itemIndexes])
return YES;
[usedIndexes addIndexes: itemIndexes];
}
}
return NO;
}
if (action == @selector(setPriority:))
{
if ([fFileOutline numberOfSelectedRows] <= 0)
{
[menuItem setState: NSOffState];
return NO;
}
//determine which priorities are checked
NSIndexSet * indexSet = [fFileOutline selectedRowIndexes];
BOOL current = NO, other = NO;
int i, priority;
Torrent * torrent = [fTorrents objectAtIndex: 0];
if (menuItem == fFilePriorityHigh)
priority = PRIORITY_HIGH;
else if (menuItem == fFilePriorityLow)
priority = PRIORITY_LOW;
else
priority = PRIORITY_NORMAL;
for (i = [indexSet firstIndex]; i != NSNotFound && (!current || !other); i = [indexSet indexGreaterThanIndex: i])
{
if ([torrent hasFilePriority: priority forIndexes: [[fFileOutline itemAtRow: i] objectForKey: @"Indexes"]])
current = YES;
else
other = YES;
}
[menuItem setState: current ? (other ? NSMixedState : NSOnState) : NSOffState];
return YES;
}
return YES;
}
@ -771,31 +828,24 @@
- (int) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
{
if (!item)
return fFiles ? [fFiles count] : 1;
return [fFiles count];
return [[item objectForKey: @"IsFolder"] boolValue] ? [[item objectForKey: @"Children"] count] : 0;
}
- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
{
return item && [[item objectForKey: @"IsFolder"] boolValue];
return [[item objectForKey: @"IsFolder"] boolValue];
}
- (id) outlineView: (NSOutlineView *) outlineView child: (int) index ofItem: (id) item
{
if (!fFiles)
return nil;
return [(item ? [item objectForKey: @"Children"] : fFiles) objectAtIndex: index];
}
- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn
byItem: (id) item
- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
{
if (!item)
return nil;
if ([[tableColumn identifier] isEqualToString: @"Check"])
return [item objectForKey: @"Check"];
return [NSNumber numberWithInt: [[fTorrents objectAtIndex: 0] checkForFiles: [item objectForKey: @"Indexes"]]];
else
return item;
}
@ -803,26 +853,27 @@
- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell
forTableColumn: (NSTableColumn *) tableColumn item: (id) item
{
if ([[tableColumn identifier] isEqualToString: @"Name"])
NSString * identifier = [tableColumn identifier];
if ([identifier isEqualToString: @"Name"])
{
if (!item)
return;
[cell setImage: [[item objectForKey: @"IsFolder"] boolValue] ? fFolderIcon : [item objectForKey: @"Icon"]];
}
else if ([[tableColumn identifier] isEqualToString: @"Check"])
{
/*if (!item)
if ([[item objectForKey: @"IsFolder"] boolValue])
[cell setImage: nil];
else
{
[(NSButtonCell *)cell setImagePosition: NSNoImage];
[cell setEnabled: NO];
return;
[cell setImage: [item objectForKey: @"Icon"]];
[(FileBrowserCell *)cell setProgress: [[fTorrents objectAtIndex: 0] fileProgress:
[[item objectForKey: @"Indexes"] firstIndex]]];
}
[(NSButtonCell *)cell setImagePosition: NSImageOnly];
[cell setEnabled: [[item objectForKey: @"IsFolder"] boolValue] ? [[item objectForKey: @"Remaining"] intValue] > 0
: [[item objectForKey: @"Progress"] floatValue] < 1.0];*/
[(NSButtonCell *)cell setImagePosition: NSNoImage];
}
else if ([identifier isEqualToString: @"Check"])
[cell setEnabled: [[fTorrents objectAtIndex: 0] canChangeDownloadCheckForFiles: [item objectForKey: @"Indexes"]]];
else if ([identifier isEqualToString: @"Priority"])
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSIndexSet * indexeSet = [item objectForKey: @"Indexes"];
[(NSSegmentedCell *)cell setSelected: [torrent hasFilePriority: PRIORITY_LOW forIndexes: indexeSet] forSegment: 0];
[(NSSegmentedCell *)cell setSelected: [torrent hasFilePriority: PRIORITY_NORMAL forIndexes: indexeSet] forSegment: 1];
[(NSSegmentedCell *)cell setSelected: [torrent hasFilePriority: PRIORITY_HIGH forIndexes: indexeSet] forSegment: 2];
}
else;
}
@ -830,24 +881,63 @@
- (void) outlineView: (NSOutlineView *) outlineView setObjectValue: (id) object
forTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
int state = [object intValue] != NSOffState ? NSOnState : NSOffState;
[torrent setFileCheckState: state forFileItem: item];
NSMutableDictionary * topItem = [torrent resetFileCheckStateForItemParent: item];
[fFileOutline reloadItem: topItem reloadChildren: YES];
NSString * identifier = [tableColumn identifier];
if ([identifier isEqualToString: @"Check"])
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
[torrent setFileCheckState: [object intValue] != NSOffState ? NSOnState : NSOffState
forIndexes: [item objectForKey: @"Indexes"]];
[fFileOutline reloadData];
}
else if ([identifier isEqualToString: @"Priority"])
{
int priority = [object intValue], actualPriority;
if (priority == 0)
actualPriority = PRIORITY_LOW;
else if (priority == 2)
actualPriority = PRIORITY_HIGH;
else
actualPriority = PRIORITY_NORMAL;
[[fTorrents objectAtIndex: 0] setFilePriority: actualPriority forIndexes: [item objectForKey: @"Indexes"]];
[fFileOutline reloadData];
}
else;
}
- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
tableColumn: (NSTableColumn *) tableColumn item: (id) item mouseLocation: (NSPoint) mouseLocation
{
if (!item)
return nil;
NSString * ident = [tableColumn identifier];
if ([ident isEqualToString: @"Name"])
return [[[fTorrents objectAtIndex: 0] downloadFolder] stringByAppendingPathComponent: [item objectForKey: @"Path"]];
else if ([ident isEqualToString: @"Check"])
{
int check = [cell state];
if (check == NSOffState)
return NSLocalizedString(@"Don't Download", "Inspector -> files tab -> tooltip");
else if (check == NSMixedState)
return NSLocalizedString(@"Download Some", "Inspector -> files tab -> tooltip");
else
return NSLocalizedString(@"Download", "Inspector -> files tab -> tooltip");
}
else if ([ident isEqualToString: @"Priority"])
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSIndexSet * indexSet = [item objectForKey: @"Indexes"];
BOOL low = [torrent hasFilePriority: PRIORITY_LOW forIndexes: indexSet],
normal = [torrent hasFilePriority: PRIORITY_NORMAL forIndexes: indexSet],
high = [torrent hasFilePriority: PRIORITY_HIGH forIndexes: indexSet];
if (low && !normal && !high)
return NSLocalizedString(@"Low Priority", "Inspector -> files tab -> tooltip");
else if (!low && normal && !high)
return NSLocalizedString(@"Normal Priority", "Inspector -> files tab -> tooltip");
else if (!low && !normal && high)
return NSLocalizedString(@"High Priority", "Inspector -> files tab -> tooltip");
else
return NSLocalizedString(@"Multiple Priorities", "Inspector -> files tab -> tooltip");
}
else
return nil;
}
@ -860,11 +950,6 @@
return [outlineView rowHeight];
}
- (BOOL) outlineView: (NSOutlineView *) outlineView shouldSelectItem: (id) item
{
return item != nil;
}
- (NSArray *) peerSortDescriptors
{
NSMutableArray * descriptors = [NSMutableArray array];
@ -917,6 +1002,46 @@
[[fFileOutline itemAtRow: i] objectForKey: @"Path"]] inFileViewerRootedAtPath: nil];
}
- (void) setCheck: (id) sender
{
int state = sender == fFileCheckItem ? NSOnState : NSOffState;
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSIndexSet * indexSet = [fFileOutline selectedRowIndexes], * itemIndexes;
NSMutableIndexSet * usedIndexes = [NSMutableIndexSet indexSet];
int i;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
itemIndexes = [[fFileOutline itemAtRow: i] objectForKey: @"Indexes"];
if (![usedIndexes containsIndexes: itemIndexes])
{
[torrent setFileCheckState: state forIndexes: itemIndexes];
[usedIndexes addIndexes: itemIndexes];
}
}
[fFileOutline reloadData];
}
- (void) setPriority: (id) sender
{
int priority;
if (sender == fFilePriorityHigh)
priority = PRIORITY_HIGH;
else if (sender == fFilePriorityLow)
priority = PRIORITY_LOW;
else
priority = PRIORITY_NORMAL;
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSIndexSet * indexSet = [fFileOutline selectedRowIndexes];
int i;
for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[torrent setFilePriority: priority forIndexes: [[fFileOutline itemAtRow: i] objectForKey: @"Indexes"]];
[fFileOutline reloadData];
}
- (void) setLimitSetting: (id) sender
{
BOOL upload = sender == fUploadLimitPopUp;

View File

@ -34,7 +34,7 @@
* fBlue1Piece, * fBlue2Piece, * fBlue3Piece, * fBlue4Piece;
Torrent * fTorrent;
int fNumPieces, fAcross, fWidth, fExtraBorder;
int fNumPieces, fAcross, fWidth, fExtraBorder;
IBOutlet NSImageView * fImageView;
}

View File

@ -43,6 +43,8 @@
- (void) awakeFromNib
{
#warning NSRectFill
NSSize size = [fImageView bounds].size;
NSBezierPath * bp = [NSBezierPath bezierPathWithRect: [fImageView bounds]];

View File

@ -1,4 +1,4 @@
/* Localized versions of Info.plist keys */
CFBundleName = "Transmission";
NSHumanReadableCopyright = "Copyright 2005-2007 Eric Petit";
NSHumanReadableCopyright = "Copyright 2005-2007 The Transmission Project";

View File

@ -47,7 +47,7 @@
if (size < 1024)
return [NSString stringWithFormat: NSLocalizedString(@"%lld bytes", "File size"), size];
float convertedSize = (float) size;
float convertedSize = (float)size;
NSString * unit;
if (size < 1048576)
{

View File

@ -25,6 +25,10 @@
#import <Cocoa/Cocoa.h>
#import <transmission.h>
#define PRIORITY_LOW -1
#define PRIORITY_NORMAL 0
#define PRIORITY_HIGH 1
#define INVALID -99
@interface Torrent : NSObject
@ -53,7 +57,7 @@
NSImage * fIcon, * fIconFlipped, * fIconSmall;
NSMutableString * fNameString, * fProgressString, * fStatusString, * fShortStatusString, * fRemainingTimeString;
NSArray * fFileList, * fFileFlatList;
NSArray * fFileList, * fFlatFileList;
int fUploadLimit, fDownloadLimit;
float fRatioLimit;
@ -93,8 +97,6 @@
- (void) resetCache;
- (BOOL) allDownloaded;
- (float) ratio;
- (int) ratioSetting;
- (void) setRatioSetting: (int) setting;
@ -162,9 +164,9 @@
- (BOOL) isPaused;
- (BOOL) isWaitingToChecking;
- (BOOL) isChecking;
- (BOOL) allDownloaded;
- (BOOL) isError;
- (NSString *) errorMessage;
- (BOOL) justFinished;
- (NSArray *) peers;
@ -186,12 +188,12 @@
- (int) peersUploading;
- (int) peersDownloading;
- (float) downloadRate;
- (float) uploadRate;
- (uint64_t) downloadedValid;
- (uint64_t) downloadedTotal;
- (uint64_t) uploadedTotal;
- (float) swarmSpeed;
- (float) downloadRate;
- (float) uploadRate;
- (uint64_t) downloadedValid;
- (uint64_t) downloadedTotal;
- (uint64_t) uploadedTotal;
- (float) swarmSpeed;
- (BOOL) pex;
- (void) setPex: (BOOL) setting;
@ -201,9 +203,12 @@
- (NSArray *) fileList;
- (int) fileCount;
- (BOOL) updateFileProgress;
- (void) setFileCheckState: (int) state forFileItem: (NSMutableDictionary *) item;
- (NSMutableDictionary *) resetFileCheckStateForItemParent: (NSMutableDictionary *) originalChild;
- (float) fileProgress: (int) index;
- (int) checkForFiles: (NSIndexSet *) indexSet;
- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet;
- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet;
- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet;
- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet;
- (NSDate *) dateAdded;
- (NSDate *) dateCompleted;

View File

@ -43,12 +43,14 @@ static int static_lastid = 0;
checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
pex: (NSNumber *) pex
waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue
filesShouldDownload: (NSArray *) filesShouldDownload filePriorities: (NSArray *) filePriorities;
- (void) historyFilePriorities: (NSMutableArray *) history forItems: (NSArray *) items;
- (void) createFileList;
- (void) createFileListShouldDownload: (NSArray *) filesShouldDownload priorities: (NSArray *) filePriorities;
- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
fileSize: (uint64_t) size state: (int) state flatList: (NSMutableArray *) flatList;
withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
flatList: (NSMutableArray *) flatList fileSize: (uint64_t) size index: (int) index priority: (int) priority;
- (NSImage *) advancedBar;
- (void) trashFile: (NSString *) path;
@ -79,7 +81,8 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
checkUpload: nil uploadLimit: nil
checkDownload: nil downloadLimit: nil
pex: nil
waitToStart: nil orderValue: nil];
waitToStart: nil orderValue: nil
filesShouldDownload: nil filePriorities: nil];
if (self)
{
@ -109,7 +112,9 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
downloadLimit: [history objectForKey: @"DownloadLimit"]
pex: [history objectForKey: @"Pex"]
waitToStart: [history objectForKey: @"WaitToStart"]
orderValue: [history objectForKey: @"OrderValue"]];
orderValue: [history objectForKey: @"OrderValue"]
filesShouldDownload: [history objectForKey: @"FilesShouldDownload"]
filePriorities: [history objectForKey: @"FilePriorities"]];
if (self)
{
@ -167,7 +172,24 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
[NSNumber numberWithInt: fCheckDownload], @"CheckDownload",
[NSNumber numberWithInt: fDownloadLimit], @"DownloadLimit",
[NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
[self orderValue], @"OrderValue", nil];
[self orderValue], @"OrderValue",
nil];
//set file should download
int fileCount = [self fileCount];
NSMutableArray * filesShouldDownload = [NSMutableArray arrayWithCapacity: fileCount];
tr_priority_t * priorities = tr_torrentGetFilePriorities(fHandle);
int i;
for (i = 0; i < fileCount; i++)
[filesShouldDownload addObject: [NSNumber numberWithBool: priorities[i] != TR_PRI_DND]];
free(priorities);
[history setObject: filesShouldDownload forKey: @"FilesShouldDownload"];
//set file priorities
NSMutableArray * filePriorities = [NSMutableArray arrayWithCapacity: fileCount];
[self historyFilePriorities: filePriorities forItems: fFileList];
[history setObject: filePriorities forKey: @"FilePriorities"];
if (fUseIncompleteFolder)
[history setObject: fIncompleteFolder forKey: @"IncompleteFolder"];
@ -223,7 +245,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
[fRemainingTimeString release];
[fFileList release];
[fFileFlatList release];
[fFlatFileList release];
[fBitmap release];
free(fPieces);
@ -284,7 +306,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
fStat = tr_torrentStat(fHandle);
//notification when downloading finished
if ([self justFinished])
if (tr_getComplete(fHandle) || tr_getDone(fHandle))
{
BOOL canMove = YES;
@ -322,6 +344,9 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
fStat = tr_torrentStat(fHandle);
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
}
else if (tr_getIncomplete(fHandle))
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
else;
//check to stop for ratio
float stopRatio;
@ -345,6 +370,12 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
[progressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
[NSString stringForFileSize: [self downloadedValid]],
[NSString stringForFileSize: [self size]], 100.0 * [self progress]];
else if ([self progress] < 1.0)
[progressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
"Torrent -> progress string"),
[NSString stringForFileSize: [self downloadedValid]], [NSString stringForFileSize: [self size]],
100.0 * [self progress], [NSString stringForFileSize: [self uploadedTotal]],
[NSString stringForRatio: [self ratio]]];
else
[progressString appendFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
[NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
@ -439,6 +470,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
break;
case TR_STATUS_SEED:
case TR_STATUS_DONE:
[statusString setString: @""];
if ([self totalPeers] != 1)
[statusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
@ -518,13 +550,12 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
[self name], @"Name",
[NSNumber numberWithBool: [self isSeeding]], @"Seeding",
[NSNumber numberWithFloat: [self progress]], @"Progress",
[NSNumber numberWithFloat: (float)fStat->left/[self size]], @"Left",
[NSNumber numberWithBool: [self isActive]], @"Active",
[NSNumber numberWithBool: [self isError]], @"Error", nil];
if ([self isSeeding])
{
[info setObject: [NSNumber numberWithFloat: [self progressStopRatio]] forKey: @"ProgressStopRatio"];
}
if (![fDefaults boolForKey: @"SmallView"])
{
@ -611,11 +642,6 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
tr_torrentRemoveFastResume(fHandle);
}
- (BOOL) allDownloaded
{
return [self progress] >= 1.0;
}
- (float) ratio
{
return fStat->ratio;
@ -1042,6 +1068,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
break;
case TR_STATUS_SEED:
case TR_STATUS_DONE:
return NSLocalizedString(@"Seeding", "Torrent -> status string");
break;
@ -1056,7 +1083,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
- (float) progress
{
return fStat->progress;
return fStat->percentComplete;
}
- (int) eta
@ -1071,7 +1098,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
- (BOOL) isSeeding
{
return fStat->status == TR_STATUS_SEED;
return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
}
- (BOOL) isPaused
@ -1089,6 +1116,11 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return fStat->status == TR_STATUS_CHECK;
}
- (BOOL) allDownloaded
{
return fStat->cpStatus != TR_CP_INCOMPLETE;
}
- (BOOL) isError
{
return fStat->error != 0;
@ -1107,11 +1139,6 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return error;
}
- (BOOL) justFinished
{
return tr_getFinished(fHandle);
}
- (NSArray *) peers
{
int totalPeers, i;
@ -1237,7 +1264,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
- (uint64_t) downloadedValid
{
return fInfo->totalSize - fStat->left;
return fStat->downloadedValid;
}
- (uint64_t) downloadedTotal
@ -1289,88 +1316,94 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return fInfo->fileCount;
}
- (BOOL) updateFileProgress
- (float) fileProgress: (int) index
{
BOOL change = NO;
float * progress = tr_torrentCompletion(fHandle);
NSNumber * progressNum;
NSMutableDictionary * item, * dict;
int i, fileCount = [self fileCount];
for (i = 0; i < fileCount; i++)
{
if (!(progressNum = [[fFileFlatList objectAtIndex: i] objectForKey: @"Progress"])
|| [progressNum floatValue] != progress[i])
{
item = [fFileFlatList objectAtIndex: i];
[item setObject: [NSNumber numberWithFloat: progress[i]] forKey: @"Progress"];
change = YES;
if (progress[i] == 1.0)
{
dict = item;
while ((dict = [dict objectForKey: @"Parent"]))
[dict setObject: [NSNumber numberWithInt: [[dict objectForKey: @"Remaining"] intValue]-1]
forKey: @"Remaining"];
[item setObject: [NSNumber numberWithInt: NSOnState] forKey: @"Check"];
[self resetFileCheckStateForItemParent: item];
}
}
}
free(progress);
return change;
return tr_torrentFileCompletion(fHandle, index);
}
- (void) setFileCheckState: (int) state forFileItem: (NSMutableDictionary *) item
- (int) checkForFiles: (NSIndexSet *) indexSet
{
[item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
if (![[item objectForKey: @"IsFolder"] boolValue])
return;
NSMutableDictionary * child;
NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
while ((child = [enumerator nextObject]))
if (state != [[child objectForKey: @"Check"] intValue])
[self setFileCheckState: state forFileItem: child];
BOOL onState = NO, offState = NO;
int index;
for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
{
if (tr_torrentGetFilePriority(fHandle, index) != TR_PRI_DND || [self fileProgress: index] >= 1.0)
onState = YES;
else
offState = YES;
if (onState == offState)
return NSMixedState;
}
return onState ? NSOnState : NSOffState;
}
- (NSMutableDictionary *) resetFileCheckStateForItemParent: (NSMutableDictionary *) originalChild
- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
{
NSMutableDictionary * item;
if (!(item = [originalChild objectForKey: @"Parent"]))
return originalChild;
int state = INVALID;
NSMutableDictionary * child;
NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
while ((child = [enumerator nextObject]))
int index;
for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
if ([self fileProgress: index] < 1.0)
return YES;
return NO;
}
- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
{
int index;
for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
{
if (state == INVALID)
tr_priority_t actualPriority;
if (state == NSOnState)
{
state = [[child objectForKey: @"Check"] intValue];
if (state == NSMixedState)
break;
int priority = [[[fFlatFileList objectAtIndex: index] objectForKey: @"Priority"] intValue];
if (priority == PRIORITY_HIGH)
actualPriority = TR_PRI_HIGH;
else if (priority == PRIORITY_LOW)
actualPriority = TR_PRI_LOW;
else
actualPriority = TR_PRI_NORMAL;
}
else if (state != [[child objectForKey: @"Check"] intValue])
{
state = NSMixedState;
break;
}
else;
else
actualPriority = TR_PRI_DND;
tr_torrentSetFilePriority(fHandle, index, actualPriority);
}
if (state != [[item objectForKey: @"Check"] intValue])
#warning when going seeding to download, update queue
[self update];
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self]; //for paused torrents
}
- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
{
NSNumber * priorityValue = [NSNumber numberWithInt: priority];
int index;
for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
{
[item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
return [self resetFileCheckStateForItemParent: item];
[[fFlatFileList objectAtIndex: index] setObject: priorityValue forKey: @"Priority"];
if ([self checkForFiles: [NSIndexSet indexSetWithIndex: index]] == NSOnState)
{
tr_priority_t actualPriority;
if (priority == PRIORITY_HIGH)
actualPriority = TR_PRI_HIGH;
else if (priority == PRIORITY_LOW)
actualPriority = TR_PRI_LOW;
else
actualPriority = TR_PRI_NORMAL;
tr_torrentSetFilePriority(fHandle, index, actualPriority);
}
}
else
return originalChild;
}
- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
{
int index;
for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
if (priority == [[[fFlatFileList objectAtIndex: index] objectForKey: @"Priority"] intValue])
return YES;
return NO;
}
- (NSDate *) dateAdded
@ -1462,6 +1495,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
pex: (NSNumber *) pex
waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue
filesShouldDownload: (NSArray *) filesShouldDownload filePriorities: (NSArray *) filePriorities;
{
if (!(self = [super init]))
return nil;
@ -1536,7 +1570,7 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
[self createFileList];
[self createFileListShouldDownload: filesShouldDownload priorities: filePriorities];
//set up advanced bar
fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
@ -1552,15 +1586,17 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
return self;
}
- (void) createFileList
- (void) createFileListShouldDownload: (NSArray *) filesShouldDownload priorities: (NSArray *) filePriorities
{
int count = [self fileCount], i;
tr_file_t * file;
NSMutableArray * pathComponents;
NSString * path;
int priority;
tr_priority_t actualPriority;
NSMutableArray * fileList = [[NSMutableArray alloc] init],
* fileFlatList = [[NSMutableArray alloc] initWithCapacity: count];
* flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
for (i = 0; i < count; i++)
{
@ -1575,20 +1611,35 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
else
path = @"";
priority = filePriorities ? [[filePriorities objectAtIndex: i] intValue] : PRIORITY_NORMAL;
[self insertPath: pathComponents forSiblings: fileList withParent: nil previousPath: path
fileSize: file->length state: NSOnState flatList: fileFlatList];
flatList: flatFileList fileSize: file->length index: i priority: priority];
[pathComponents autorelease];
if (!filesShouldDownload || [[filesShouldDownload objectAtIndex: i] boolValue])
{
if (priority == PRIORITY_HIGH)
actualPriority = TR_PRI_HIGH;
else if (priority == PRIORITY_LOW)
actualPriority = TR_PRI_LOW;
else
actualPriority = TR_PRI_NORMAL;
}
else
actualPriority = TR_PRI_DND;
tr_torrentSetFilePriority(fHandle, i, actualPriority);
}
fFileList = [[NSArray alloc] initWithArray: fileList];
[fileList release];
fFileFlatList = [[NSArray alloc] initWithArray: fileFlatList];
[fileFlatList release];
fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
[flatFileList release];
}
- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
fileSize: (uint64_t) size state: (int) state flatList: (NSMutableArray *) flatList
flatList: (NSMutableArray *) flatList fileSize: (uint64_t) size index: (int) index priority: (int) priority
{
NSString * name = [components objectAtIndex: 0];
BOOL isFolder = [components count] > 1;
@ -1608,42 +1659,48 @@ static uint32_t kRed = BE(0xFF6450FF), //255, 100, 80
if (!dict)
{
dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
[NSNumber numberWithBool: isFolder], @"IsFolder",
currentPath, @"Path", nil];
[NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
[siblings addObject: dict];
if (isFolder)
{
[dict setObject: [NSMutableArray array] forKey: @"Children"];
[dict setObject: [NSNumber numberWithInt: 1] forKey: @"Remaining"];
[dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
}
else
{
[flatList addObject: dict];
[dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
[dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
[dict setObject: [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]] forKey: @"Icon"];
[dict setObject: [NSNumber numberWithInt: priority] forKey: @"Priority"];
[flatList addObject: dict];
}
if (parent)
[dict setObject: parent forKey: @"Parent"];
[dict setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
}
else
{
if (isFolder)
[dict setObject: [NSNumber numberWithInt: [[dict objectForKey: @"Remaining"] intValue]+1] forKey: @"Remaining"];
int dictState = [[dict objectForKey: @"Check"] intValue];
if (dictState != NSMixedState && dictState != state)
[dict setObject: [NSNumber numberWithInt: NSMixedState] forKey: @"Check"];
}
[[dict objectForKey: @"Indexes"] addIndex: index];
if (isFolder)
{
[components removeObjectAtIndex: 0];
[self insertPath: components forSiblings: [dict objectForKey: @"Children"]
withParent: dict previousPath: currentPath fileSize: size state: state flatList: flatList];
withParent: dict previousPath: currentPath flatList: flatList fileSize: size index: index priority: priority];
}
}
- (void) historyFilePriorities: (NSMutableArray *) history forItems: (NSArray *) items
{
NSEnumerator * enumerator = [items objectEnumerator];
NSDictionary * item;
while ((item = [enumerator nextObject]))
{
if (![[item objectForKey: @"IsFolder"] boolValue])
[history addObject: [item objectForKey: @"Priority"]];
else
[self historyFilePriorities: history forItems: [item objectForKey: @"Children"]];
}
}

View File

@ -31,7 +31,7 @@
@interface TorrentCell : NSCell
{
NSImage * fErrorImage;
CTGradient * fWhiteGradient, * fGrayGradient, * fLightGreenGradient,
CTGradient * fWhiteGradient, * fGrayGradient, * fLightGrayGradient, * fLightGreenGradient,
* fGreenGradient, * fBlueGradient, * fTransparentGradient;
NSUserDefaults * fDefaults;
}

View File

@ -46,8 +46,9 @@
{
fDefaults = [NSUserDefaults standardUserDefaults];
fWhiteGradient = [[CTGradient aquaNormalGradient] retain];
fWhiteGradient = [[CTGradient progressWhiteGradient] retain];
fGrayGradient = [[CTGradient progressGrayGradient] retain];
fLightGrayGradient = [[CTGradient progressLightGrayGradient] retain];
fBlueGradient = [[CTGradient progressBlueGradient] retain];
fGreenGradient = [[CTGradient progressGreenGradient] retain];
fLightGreenGradient = [[CTGradient progressLightGreenGradient] retain];
@ -81,25 +82,41 @@
barBounds = NSMakeRect(point.x, point.y - BAR_HEIGHT, width, BAR_HEIGHT);
completeBounds = barBounds;
float progress = [[info objectForKey: @"Progress"] floatValue];
completeBounds.size.width = progress * width;
if (progress < 1.0)
{
[fWhiteGradient fillRect: barBounds angle: -90];
float left = [[info objectForKey: @"Left"] floatValue];
if ((progress + left) < 1.0)
{
NSRect blankBounds = barBounds;
blankBounds.origin.x += width * (progress + left);
blankBounds.size.width = width * ((1.0 - progress) - left);
[fLightGrayGradient fillRect: blankBounds angle: -90];
}
}
if ([[info objectForKey: @"Seeding"] boolValue])
{
completeBounds.size.width = width * [[info objectForKey: @"ProgressStopRatio"] floatValue];
NSRect ratioBounds = completeBounds;
ratioBounds.size.width *= [[info objectForKey: @"ProgressStopRatio"] floatValue];
if (completeBounds.size.width < barBounds.size.width)
[fLightGreenGradient fillRect: barBounds angle: -90];
[fGreenGradient fillRect: completeBounds angle: -90];
if (ratioBounds.size.width < completeBounds.size.width)
[fLightGreenGradient fillRect: completeBounds angle: -90];
[fGreenGradient fillRect: ratioBounds angle: -90];
}
else
{
completeBounds.size.width = [[info objectForKey: @"Progress"] floatValue] * width;
if (completeBounds.size.width < barBounds.size.width)
[fWhiteGradient fillRect: barBounds angle: -90];
if ([[info objectForKey: @"Active"] boolValue])
[fBlueGradient fillRect: completeBounds angle: -90];
else
[fGrayGradient fillRect: completeBounds angle: -90];
}
[[NSColor colorWithDeviceWhite: 0.0 alpha: 0.2] set];
[NSBezierPath strokeRect: NSInsetRect(barBounds, 0.5, 0.5)];
}

View File

@ -239,7 +239,6 @@
NSRect rect;
Torrent * torrent;
NSImage * image;
NSRect buttonRect = NSMakeRect(0, 0, BUTTON_WIDTH, BUTTON_WIDTH);
[super drawRect: r];
@ -264,13 +263,11 @@
image = nil;
if (image)
[image compositeToPoint: NSMakePoint(rect.origin.x, NSMaxY(rect)) fromRect: buttonRect
operation: NSCompositeSourceOver];
[image compositeToPoint: NSMakePoint(rect.origin.x, NSMaxY(rect)) operation: NSCompositeSourceOver];
rect = [self revealRectForRow: i];
image = NSPointInRect(fClickPoint, rect) ? fRevealOnIcon : fRevealOffIcon;
[image compositeToPoint: NSMakePoint(rect.origin.x, NSMaxY(rect)) fromRect: buttonRect
operation: NSCompositeSourceOver];
[image compositeToPoint: NSMakePoint(rect.origin.x, NSMaxY(rect)) operation: NSCompositeSourceOver];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -12,6 +12,9 @@
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">
<h1>Why is my download so slow? </h1>

View File

@ -14,7 +14,11 @@
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
@ -78,6 +82,18 @@
</td>
</tr>
<tr>
<td class="blue" valign="top">
<p><a href="usingt.html">Managing your transfers</a></p>
</td>
<td class="blue" valign="top" width="18"></td>
<td class="blue" valign="top" width="250">
<p class="seealso">&nbsp;</p>
</td>
</tr>
<tr>
<td class="blue" valign="top" width="135">
<p><a href="Speed.html">Maximizing download speed</a></p>

View File

@ -9,7 +9,11 @@
<div id="mainbox">
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">

View File

@ -11,6 +11,9 @@
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">
<h1>How do I manually recheck my files?</h1>
@ -24,18 +27,9 @@
<p>To do this:
<div summary="To do this" id="taskbox">
<ol>
<li>In Transmission, click on the magnifying glass next to the torrent in question.
<p>This should open a Finder window which displays all the files in your torrent.</li>
<li>Select the torrent in Transmission and click "Remove".
<p>NB: make sure you keep a copy of the torrent file - you'll need it later! </li>
<li>Open the Terminal. You can find it in /Applications/Utilities. </li>
<li>Once inside the Terminal, type "touch " (including a space). Do not hit Enter yet. </li>
<li>Drag any one of the files you are downloading into the Terminal window (from the Finder window you opened in step 1).
<p>The file path should automagically appear. </li>
<li>Now hit "Enter" inside the terminal. </li>
<li>Quit Terminal. </li>
<li>Reload the torrent file in question into Transmission.
<p>Transmission should start rechecking your files!</li>
<li>Select the relevant torrent.</li>
<li>Pause it, and then go to the Transfers menu >> Remove fast resume cache.</li>
<li>Start the torrent again. Transmission will check it first.</li>
</ol>
</div>

View File

@ -10,11 +10,18 @@
<A NAME="GettingStarted"></A>
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="mainbox">
<div id="caticon">
<img src="../gfx/TransmissionIcon.png" alt="Adium X icon" height="32" width="32" border="0">
</div>
<div id="pagetitle">
<h1>Welcome to Transmission! </h1>
</div>
@ -28,12 +35,32 @@
</td>
</tr>
<tr>
<td valign="top" width="150"><img src="../gfx/torrentlife.png" height="auto" width="150" border="0"></td>
<td valign="top" width="150"><img src="../gfx/open.png" height="auto" width="150" border="0"></td>
<td valign="top">
<p>You'll need to download a torrent file (extension .torrent). These are commonly found at <a href="http://en.wikipedia.org/wiki/BitTorrent_tracker">'tracker' websites</a>. Torrent files contain information about the actual file you want to download (eg a movie).
<p>Once you have the torrent file, open it in Transmission - downloading should start immediately. Transmission can even watch for torrent files and then open them automatically via Preferences >> Transfers >> General.
<p>You can pause and resume transfers at any time, so long as the files remain in your download folder. If you remove a transfer, in order to resume it you will need to reopen the original torrent file in Transmission.
<p>It is good etiquette to share or 'seed' the file for a while (ie leave it uploading) once your download is complete. You can set a default ratio to automatically seed to, and then pause. This can be adjusted in Preferences >> Transfers >> Management, or in real time using the Action menu. </p>
<p>Download your file's associated 'torrent file' (extension .torrent). These are commonly found at <a href="http://en.wikipedia.org/wiki/BitTorrent_tracker">'tracker' websites</a>.
<p>Once you have the torrent file, drag it into Transmission - downloading should start immediately.
<p>You can pause and resume transfers at any time, so long as the files remain in your download folder.
<p>It is good etiquette to share or 'seed' the file for a while (ie leave it uploading) once your download is complete.
<ul>
<li><a href="usingt.html">Downloading and sharing with Transmission</a></li>
</ul>
</td>
</tr>
</table>
<br>
<table class="dots" width="100%" border="0" cellspacing="0" cellpadding="0" summary="One column table with heading">
<tr>
<td class="blue" colspan="2">
<h3>Can I create my own torrents? </h3>
</td>
</tr>
<tr>
<td valign="top" width="150"><img src="../gfx/creation.png" height="auto" width="150" border="0"></td>
<td valign="top">
<p>Yes, you can share a file or folder by dragging it into Transmission. Alternatively, click 'Create' in the toolbar, and choose your file.
<p>When the dialogue box appears, enter your tracker address, comments and private status.
<p>You can change the torrent filename, as well as where it will be saved to by clicking 'Change'.
<p>Once you are done, click 'Create'. Transmission will automatically optimize the torrent file for what you are sharing.
</td>
</tr>
</table>
@ -92,6 +119,24 @@
</tr>
</table>
<br>
<table class="dots" width="100%" border="0" cellspacing="0" cellpadding="0" summary="One column table with heading">
<tr>
<td class="blue" colspan="2">
<h3>Can I choose do download specific files? </h3>
</td>
</tr>
<tr>
<td valign="top" width="150"><img src="../gfx/fileselection.png" height="auto" width="150" border="0"></td>
<td valign="top">
<p>Yes, via the Inspector. Double click any transfer to open it and then click the 'Files' tab. Simply check the boxes next to the files you want to download (the default is all of them).
You can even set a priority (red/high or yellow/low) to each file, if you want some to finish faster than others. To do so, use the selector next to the checkboxes.
<p>If you are frequently going to selectively download the files in your torrents, you might want to disable "Start Transfers when added" in Preferences >> Transfers >> Management.
This way you can organise your transfer before it starts, to avoid wasting download bandwidth.
</td>
</tr>
</table>
<br>
</div>
</body>

View File

@ -9,7 +9,11 @@
<div id="mainbox">
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">

View File

@ -9,7 +9,11 @@
<div id="mainbox">
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">

View File

@ -9,7 +9,11 @@
<div id="mainbox">
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">

View File

@ -6,18 +6,18 @@
<title>Port Forwarding FAQ</title>
</head>
<body>
<div id="mainbox"> <div id="banner"> <a name="menus"></a> <div id="machelp"><a class="bread" href="../index.html">Transmission Help</a> </div> </div>
<div id="mainbox"> <div id="banner"> <a name="menus"></a> <div id="machelp"> <a class="bread" href="../index.html">Transmission Help</a> </div> <div id="index"> <a class="leftborder" href="../html/Index2.html">Index</a></div> </div> </div>
<div id="pagetitle">
<h1>Why do I see a red dot and "Port is closed"?</h1>
</div>
<p>You haven't port forwarded correctly. Port forwarding opens a port in your firewall or router so that incoming connections from the outside world can be made with Transmission.
<p>You haven't port forwarded correctly. Port forwarding opens a port in your firewall or router so that incoming connections from the outside world can be made with Transmission. If the port is forwarded, other people in the torrent can see you, thus increasing your potential number of connections, which more importantly, may increase the speed of your download.
<p>
<div id="pagetitle">
<h1>Why do I need to Port Forward?</h1>
<h1>When do I need to Port Forward?</h1>
</div>
<p>Transmission uses a single port for all the torrents you are downloading. If the port has not been opened (ie forwarded) that means only you can make connections to others in the swarm.
If the port is forwarded, others can connect to you, thus increasing the potential number of people connected to you, and more importantly increasing the speed of your download.
<p>If you share your internet connection with a router, or if your broadband modem is a router itself, then you will need to forward Transmission's port by following the instructions below. In addition, if you have enabled the Mac OS X firewall, you will need to open a port in that too. See below for details.
<p>
<div id="pagetitle">
<h1>How do I Port Forward?</h1>
@ -34,11 +34,9 @@
</div>
<li>If you don't have a compatible router, it is simple to forward Transmission's port manually. For instructions <a href="pfrouter.html">click here</a>.</li>
<li>If you don't use a router, that is, your modem is directly connected to your computer, you'll need to open Transmission's port in the Mac OS X firewall. For instructions <a href="pffirewall.html">click here</a>.<br> Keep in mind that many DSL modems also function as routers, and hence port forwarding as per above may still be necessary, even though your computer is directly connected to the modem.<br>
NB: it is highly recommended you enable the Mac OS X firewall if you are not using a router.</li>
</ul>
<li>If you don't use a router, that is, your modem is directly connected to your computer, you'll need to open Transmission's port in the Mac OS X firewall. For instructions <a href="pffirewall.html">click here</a>.<br> <b>NB:</b> it is highly recommended you enable the Mac OS X firewall if you are not using a router.</li>
</ul> <p>Keep in mind that many DSL modems also function as routers, and hence port forwarding as per above may still be necessary, even though your computer is directly connected to the modem.
<p>
<div id="pagetitle">
<h1>How do I know if I've done it right?</h1>

View File

@ -9,7 +9,11 @@
<div id="mainbox">
<div id="banner">
<a name="menus"></a>
<div id="machelp"><a class="bread" href="../index.html">Transmission Help</a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">
@ -23,6 +27,7 @@
</ul>
<p>If you are still having problems, open the Message Log (in the Window menu) and post the debug output on the <a href="http://transmission.m0k.org/forum/viewforum.php?f=2">support forums</a>.
Make sure you pause your torrents first; then clear the log, and toggle "Automatically forward port". Post this output.
<div id="pagetitle">
<h1>Airport</h1>

View File

@ -0,0 +1,42 @@
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link media="all" rel="stylesheet" href="../styles/TransBody.css" />
<title>Using Transmission</title>
</head>
<body>
<div id="mainbox">
<A NAME="TransFAQ"></A>
<div id="banner">
<a name="menus"></a>
<div id="machelp">
<a class="bread" href="../index.html">Transmission Help</a>
</div>
<div id="index">
<a class="leftborder" href="../html/Index2.html">Index</a></div>
</div>
</div>
<div id="pagetitle">
<h1>Managing your transfers </h1>
</div>
<ul>
<li>Torrent files contain information about the actual file you want to download (eg a movie), and connect you to the swarm of peers sharing it.</li>
<li>Transmission can watch a certain folder (eg your Safari download folder) for torrent files and then open them automatically via Preferences >> Transfers >> General.</li>
<li>By default, Transmission deletes the original torrent file upon opening. If you remove a transfer, in order to resume it you will need to reopen the original torrent file in Transmission.
Simply choose 'Save a Torrent Copy as' from the File menu before deletion to avoid having to download the torrent file again.</li>
<li>Once your download is complete, you can set a default ratio to automatically seed to, and then pause.
This can be adjusted in Preferences >> Transfers >> Management, or in real time using the Action menu.</li>
<li>Both seeding and downloading transfers can be queued, and Transmission can skip over stalled transfers, in order to maximise queuing efficiency.
Queuing can be configured in Preferences >> Transfers >> Management.</li>
</ul>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More