© 2003-2004 Sarwat Khan, All rights reserved. Read the License.text file for permitted use and legal disclaimer.
Note: If you release a modified version of Tomato Torrent, do not name it Tomato Torrent. Don't use the Tomato icon either. If it's not 100% Tomato, it's not Tomato. If you want to break these rules, please ask.
I started this project because I thought that I could implement a better download window pretty quickly by seperating the Python and Cocoa code into separate processes. I implemented that solution in about three days, but I thought it would be silly to leave it alone at that. After a couple of weeks I wound up with a fairly complete GUI.
For a better idea of the features that this GUI implements, read the application's Help documentation.
In the directory containing this readme you'll find the sources and project
inside a directory named BitTorrent. The project expects to find the Python
BitTorrent sources (from bitconjurer.org/BitTorrent/)
in ../bitconjurer
. That is,
Readme.html Tomato/ Tomato.xcode/ ... other items bitconjurer/ BitTorrent/ __init__.py ... other items btmakemetafile.py ... other items
You can do this with the following. Assuming you're cd'd into the directory with this Readme,
% cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/bittorrent login (Logging in to anonymous@cvs.sourceforge.net) CVS password: [press enter for anon] % cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/bittorrent co -d bitconjurer BitTorrent cvs server: Updating bitconjurer ...
The next thing you'll need is Python 2.3. If you have Panther, great, you've got it. Otherwise, you can get it from pythonmac.org. The build process uses Python 2.3 to run a script, which is related to the output of the next step,
Next you have to define the Python interpreters installed on your system that the build system can use to compile optimized code. From the directory that contains the Tomato.xcode,
% cd Tomato % python2.3 ./TomatoTorrentAgent/Scripts/DefinePythonInterpreters.py define Defined python2.3 with zipimport: 1
One of the side effects of that is that it creates a TomatoTorrentAgent/Interpreters directory,
which contains a file that's used by one of the build scripts. If you want your build to run on Jaguar, you must run DefinePythonInterpreters with Python 2.2
and the --no-zip
option. See the section
Optimization for Specific Versions of Python.
Once all that's done you should be able to build the Tomato.xcodeproj project file.
Oh wait. One more thing.
There’s a script that should be run during the build. It probably won’t run. The reason is because I’ve probably set it to run during deployment builds only. The reason that I've probably done this is that it takes a very, very long time to run, and it only needs to run once. Unless that script runs, you won’t be able to run Tomato (unless you do a deployment build, in which case it will run).
To check if the script is going to run, do the following.
When you do a build of the TomatoTorrentAgent target, there should be a long pause, with the build status saying something like “Running Shell Script.” Now that it’s run, I suggest turning it off. Otherwise, it will run everytime you do a build because the build system can’t determine if a shell script execution is up to date, and the script itself isn’t smart enough to decide if it should run or not. What the script does is copy BitTorrent sources from the bitconjurer/ directory into the TomatoTorrentAgent framework.
With version 7 the project has been split into modular frameworks, and an application that depends on all the frameworks. You can replace the application with your own if you're not fond of it.
Target | Dependancies | Description |
---|---|---|
Tomato Torrent |
- TomatoTorrentAgent - SKFoundation - SKFoundationAdditions - SKAppKitAdditions |
This is the GUI application that the user uses. |
TomatoTorrentAgent |
- SKFoundation |
This encapsulates BitTorrent and hides Python's existence from anyone using the framework (it contains and uses all the BitTorrent python code). Creates TomatoTorrentAgent.framework. |
SKFoundation |
- SKFoundationAdditions |
Model classes and data formatters. Creates SKFoundation.framework. |
SKFoundationAdditions | Category additions to Foundation.framework (not SKFoundation). Creates SKFoundationAdditions.framework. | |
SKAppKitAdditions | Category additions to AppKit.framework. Creates SKAppKitAdditions.framework. |
If you're going to use the framework in your own app, you'll need to set up the process group and signal handlers as they are in the main.m source. More documentation would be desirable, but of course it doesn't exist yet.
It should be noted that this project was designed 'at the keyboard,' so it is not ideal or consistent at parts. It was also written with the intention of getting the most done as quickly as possible, so it is also scary at parts.
The goal of this project was to seperate the Python and Cocoa code, so that the BitTorrent code and the Macintosh GUI code can be modified and enhanced freely. The Macnitosh GUI is a pure Cocoa application that spawns Python processes to handle BitTorrent tasks. Communication between the processes is handled by the BitTorrentPosixAgent.py script/BitTorrent.agent module on the Python side and the TomatoTorrentAgent.m class on the Cocoa side.
A TomatoTorrentAgent.m instance represents a running BitTorrentPosixAgent.py process. This Python process takes requests on standard input and writes responses on standard output. Requests are simple one-line commands (or two lines if the command takes an argument) and the response is XML (a CoreFoundation property list). Consider downloading a torrent via a url. A controller in the Cocoa app will perform the following actions:
setSourceURL:
defines an internal variable inside the object.
beginDownload:
sends a request to the BitTorrentPosixAgent.py
process with the value of the internal variable; self
will get the response
of the request once it has been processed.
To get a better idea of how BitTorrentPosixAgent.py and TomatoTorrentAgent.m works, start reading through BitTorrentPosixAgent.py and agent.py to see how they're used (you can run PosixAgent on the command line and use it yourself; it works independently of the GUI). Then go through TomatoTorrentAgent.m and see how it interacts with TomatoTorrentAgent.py.
Again, TomatoTorrentAgent.m was designed at the keyboard. The interface for BitTorrent downloading, tracking, and generating are all a bit different, and they have different policies for correct usage. This may be cleaned up in the future, but the best examples for how to use TomatoTorrentAgent.m are demonstreated in the classes DownloadDocument, TrackerDocument, and GenerateTorrentsController. BitTorrent.agent is much more consistent, however.
The following source files contain logic pertaining to abstracting BitTorrent. Additionally, a custom site.py is included to set the default text encoding to UTF-8 for Mac OS X and to disable site customization.
BitTorrent.agent
. The only logic it has is about how to deal with
agent
(how to understand its response data, what kind of
data to send it). It's not exactly that pure because of the ad-hoc
nature of the project, but it's supposed to be. Uses btagent.h to
spawn agent processes.I released the first few releases of this app with compiled .pyo files for Python 2.2, which is what shipped with Jaguar. However, these files do not work with Python 2.3, which is what ships with Panther. After some thinking I decided to support both, as well as including the original source code in case if the user is using a version of Python that TomatoTorrentAgent does not recognize.
The mechanics of this stuff is actually pretty simple. Really. Please accept that before reading further. Cool? Good.
The TomatoTorrentAgent.framework may have several optimized packages of the TomatoTorrentAgent Python code. Currently, it has two optimized packages: python2.2 and python2.3. These packages are located in directories by the same names in TomatoTorrentAgent.framework/Resources/TomatoTorrentAgent/.
The low level btagent API now has a one-time initialize function that reads a property list dictionary from the framework's bundle resources that describes the available optimized packages. It compares the information described by the dictionary to the version of Python installed on the user's machine, and configures btagent globals so that btagent functions use the right package.
There's a script in BitTorrent/TomatoTorrentAgent/Scripts called DefinePythonInterpreters. This lets you define a Python interpreter that will be used at build time to compile the TomatoTorrentAgent python code into optmized .pyo code. For example,
% python2.3 ./BitTorrent/TomatoTorrentAgent/Scripts/DefinePythonInterpreters.py define Defined python2.3 with zipimport: 1 % python2.2 ./BitTorrent/TomatoTorrentAgent/Scripts/DefinePythonInterpreters.py define --no-zip Defined python2.2 with zipimport: 0
This defines your python2.3 (maybe in /usr/local/bin) executable as the executable to use to compile the .py files into .pyo files during build time. Also, the files will be packaged in a zip file and imported using zipimport. The second command uses python2.2 (maybe in /usr/bin) without zipimport, since Python 2.2 does not support zipimport.
If the user has a version of Python that btagent_initialize can't recognize, it will use the default, uncompiled package, which is located in TomatoTorrentAgent.framework/Resources/TomatoTorrentAgent/pythonZip/btagent.zip. This package requires at least Python 2.3, since Python 2.2 and earlier do not support loading modules from zip archives.
One benefit of this is that it becomes possible to include specific code for each version of Python. I don't intend to include an optimized build for every version of Python, just Python 2.2 since it can't read the zip file (and the -OO .pyo is better than shipping source), and whatever the latest version of Python is (which is currently 2.3). As Jaguar gets less popular, I may consider dropping the Python 2.2 build and changing the generic build to a non-zip build, but the downside to that is that the generic build must then be compatible with Python 2.2, and then we're only saving about 80 K anyway. Given how clean MacPython is becoming, in the future we could just ask Jaguar users to download the latest MacPython and have the agent use it if it's newer than /usr/bin/python.
Someone mentioned that they had a torrent with Unicode characters in the file name and it crashed v3. So I took a torrent, inserted some random characters from the Character Palette, and I opened the .torrent with v3. In TomatoTorrentAgent.m, an NSString instance raised an exception when asked to return a C-string representation. That was a bit short-sighted on my part...
v4 seems to work fine with Unicode, and I believe I did it properly on the
Python side. Python is now exec'd directly from /usr/bin/python
rather than through
/usr/bin/env
(for a variety of reasons), and I've set to PYTHONPATH="."
,
which I've used to replace site.py. I've defined site.py to work with Mac OS X and
BitTorrent and ignore normal site customization, and I've set the default string
encoding to UTF-8. This lets the Python code work smoothly with unicode objects.
The only question I have is what the default string encoding should be for Mac OS X. Everything points to UTF-8, but there's this undocumented ~/.CFUserTextEncoding file, and I'm not sure what happens when you work with volumes that are not Unicode-savvy. For now, I'm sticking to UTF-8, which I think will work for most people most of the time.
Well, I ended up needing a 10.1 test system for another project so I decided to see how hard it would be to get BitTorrent running on it. There's a couple issues.
The project needs to link against Carbon to have access to InternetConfig. Cocoa.framework's umbrella-ness doesn't have InternetConfig functionality in 10.1, which means that users can't just install python in /usr/bin and have the current binary work. Secondly, asprintf doesn't exist in 10.1, which is used by btagent_posix.c. That's a bit of a bummer, since I don't care enough to work around that.
The biggest problem I had is that I couldn't get the code to compile with the 10.1 Developer Tools. I didn't want to spend the time to investigate thoroughly, but the main problem seemed to be that gcc 2.95 (10.2 uses gcc 3) completely choked on Objective-C + trampolines (inner functions used as callbacks, which I use a lot). I love using inner functions, as they really make up for Apple Objective-C's lack of Blocks, but they're really, really buggy, causing all kinds of side effects with your code (BTW, if you use them, do not compile with -Os. That's a known bug.).
Anyway, that meant that if I wanted to test anything, I'd have to reboot into 10.2, compile, reboot into 10.1, run, test, reboot into 10.2, repeat. So I didn't do that very long, and as such I didn't include any resulting 10.1 code in this source distribution (However, I did find that with autologin on, rebooting was very, very quick. I enjoyed it a bit because I was actually a little proud each time. k' I'll shut up now). I got something working, and at the most I might slap something together and make one 10.1 build and leave it at that.
I would like to see much more logic put into the Python side of things — specifically the TomatoTorrentAgent.py script. For example, there's way too much intelligence in DownloadTorrentProgressController. I would prefer it if that controller just mirrored the state of the TomatoTorrentAgent process, rather than trying to figure out what state it is in. There has been some effort since the first release towards this, but I'd prefer it if the DownloadTorrentProgressController can be reduced to a bunch of data formatters.
I don't know if anyone is going to use the tracker much, but it would be very useful if the GUI could write out a shell script to run BitTorrent based on a Tracker document.