Azureus Plugin Tutorial v0.2
Azureus is my BitTorrent client of choice, but I’ve found on occasion that its functionality is a bit lacking. However, fortunately for me it is extensible via plugins, which has enabled me to add my own functionality to it.
This is a brief tutorial on how to use the Azureus plugin interface to make a simple plugin that aggregates some basic information about your torrents and logs it. Some listeners are used, and some of the basic utilities for creating views and configuration pages are also included. Since this is my first tutorial, I’m not sure it’ll be adequate, but feel free to use this page’s comment section to provide feedback on it and tell me how I went horribly, horribly wrong.
Assumptions
I assume a few things here, although I am trying to minimize them. However, I have not the time or energy to explain classpaths and jar building for every IDE, nor do I have any interest in teaching basic Java. If you need to know more about these things, there are other places to do so.
Getting Started
You’ll need a few things in order to make your plugin. Since I use Eclipse for development, there is obviously going to be a bias towards the use of that tool. It’s not perfect (I’m still trying to get debugging working right) but for simple plugins it works well.
Build Environment (Eclipse)
On the off chance you’re not using it already, I recommend you download Eclipse to start with. Install it however you like.
Libraries (Azureus and SWT)
You’ll need the source code for Azureus, since at this time there is no pure Plugin SDK for it. This leads to a bit of confusion over what parts of the code to use, but I’ll try clear that up. You can get the source here — download the source zip at the bottom of the page. This tutorial is based on the 2.2.0.0 code, but the parts I’m using should be pretty basic across at least a few versions. Unzip the source zip into [dev]/Azureus2.2.0.0source (here’s where the assumptions start — I’ll be assuming a simple and clear folder layout for your files) Also download the actual Azureus jar from the above location, and install it wherever you like — you can use the jarfile from your existing Azureus installation, which is what I did. Although you may or may not need these for your plugin (for this tutorial it’s not strictly necessary) it pays to have SWT(Standard Widget Toolkit) installed. Get it here and unzip the source package into [dev]/swt-3.0.1-win32 (Note that I assume SWT v3.0.1 which is the release version as of this writing. The version number should remain more or less irrelevant as long as it begins with 3)
Eclipse Config
Libraries
First thing you need to do with Eclipse (other IDEs are exercises left to the reader) is configure the above libraries. For each of them, you need open the Classpath Variables preferences page (Window->Preferences: Java -> Build Path -> Classpath Variables) and add the libraries:


You also have to add:
AZUREUS_SRC ([dev]/Azureus_2.2.0.0_source.zip)SWT_LIB ([dev]/swt-3.0.1-win32/swt.jar)SWT_SRC ([dev]/swt-3.0.1-win32/swtsrc.zip)
This assumes that you downloaded the source for SWT as well.
Project Layout
Create a new Java project in Eclipse, and add AZUREUS_LIB and SWT_LIB to it as well (in the Libraries tab). If you wish to have javadoc popups and source code for the SWT/Azureus libraries, you have to add source attachments (the SRC variables were for this) to the jar files. Good luck with that!
Also, in order to use the SWT you may or may not (depending on how you debug — I didn’t need this) wish to copy the swt-awt-* and swt-* libraries (dll files, on windows) into the project root. My project layout defaults to putting all source files in a src/ subdirectory off of the project root, and this is what I’ll assume. However, if it so happens that you do not do so, it’s in fact easier to export to a plugin jar. I have to jump through one extra hoop with my project layout. So I would recommend creating a flat project layout. However, I’ll just assume that all of your source files are in [src]/[stuff] from here on out.
Down to Brass Tacks
plugin.properties
The first thing that every plugin needs is a plugin.properties file:
plugin.class=edu.azureus.example.pluginplugin.name=autostopplugin.langfile=edu.azureus.example.Messagesplugin.id=autostop
Download this code: plugin.properties
The lines are as follows:- plugin.class: (REQUIRED) This is simply the base class of the plugin. This class must implement
org.gudy.azureus2.plugins.Plugin. - plugin.name: (REQUIRED) At this time I’m not 100% certain of the difference between this one and plugin.id, so I suggest that they both be the same for now.
- plugin.langfile: This optional value indicates the package and name of the file to be used for internationalization. As far as the naming format goes, "Messages" is the basename, adding
_[language]_[country code]onto it customizes it for local languages. Bear in mind, I have only worked in english, don’t quote me on that format. - plugin.id: I assume that this must be unique, but I am not sure what its relation to plugin.name is, and as such I keep it the same.
Messages.properties
example.name=Azureus Example Plugin - Status loggerexample.prefix=Log prefixexample.defaultPrefix=Status Changeexample.enabled=Enabled
Download this code: Messages.properties
The package I am using (edu.azureus.example) can be anything you want it to be, so long as you keep it in sync with the plugin.properties file.
plugin.java
Download: plugin.java
This is a skeleton of a plugin for logging status changes in torrents. It has a few features of immediate interest:
Lines 3-8: Import statements
These are a subset of the plugin library imports. Assuming that you are using Eclipse, you shouldn’t have to worry about these, it’ll take care of it for you. However, I cannot assume that you’re doing the right thing here
Line 10,11: Class declaration
As you can see, the class implements the
org.gudy.azureus2.plugins.Plugin,org.gudy.azureus2.plugins.download.DownloadManagerListener, andorg.gudy.azureus2.plugins.download.DownloadListenerinterfaces. These are the most basic of the lot, required for almost any operation on the torrent list.Lines 12-20: Property names
These constants identify values in
Messages.properties. They are used for internationalization and string tables for the application.
The methods are, in order of importance:
void initialize(PluginInterface)This method initializes the plugin. What’s going on in here is a bit complex, I’ll get to that later.
void downloadAdded(Download download)This method is called whenever the downloadManager adds another download. All it is doing here is adding this plugin as a download listener for that download, which otherwise doesn’t happen.
void downloadRemoved(Download download)This should be self-explanatory.
void stateChanged(Download download, int old_state, int new_state)The actual work takes place here, in this method inherited from
DownloadListener.This method checks to see if theenabledproperty is set to true (See below) and if it is, logs the state change to the logger (Also, see below).Downloadhas several constants regarding states beginning withST_and an array of namesST_NAMESthat are used here.void positionChanged(Download download, int oldPosition, int newPosition)We don’t do anything with this in this example, but it would be used if you wanted to monitor the queue of downloads for changes in ordering.
initialize
As promised, here’s a rundown on what is going on in initialize()
(line 43) First, we store the
pluginInterfaceobject for future use. Although nothing is done with it in this plugin, you may want subordinate classes to be able to get at it. We may also need it in other parts of this plugin class.(line 44) For convenience (and because I hate typing) I keep the
localeUtilitiesreferenced as well. This is necessary for any translates strings that you use manually,.(line 45) Set up the logger.
Note that log is a
LoggerChannelobject, which we assign by first getting theLoggerfor our plugin from thepluginInterface, and then a new channel which we can name whatever we like (although something at least a bit unique is probably a good idea here).(lines 47-51) Set up the plugin log viewer.
We use
BasicPluginViewModelto set up a simple view for our plugin. This is gotten through the UI Manager of thepluginInterface, as should be pretty obious. Thelu.getLocalisedMessage(BASENAME + "name")call is the basic format for all string use, this takes the propertyexample.namefromMessages.propertiesand uses that string.vm.getActivity()andvm.getProgress()calls disable elements of the view that we won’t be needing for this plugin. You don’t have to do anything else to make this view show up — by doing this you have already created the view and added it to the Plugins menu in Azureus.(lines 53-63) Set up the log listener.
Here, we add a
LoggerChannelListenerto the log we created above. Although we could do fancy things with it, there’s not much point, and an anonymous class does the trick just as well. Either way, the listener must implementLoggerChannelListenerat least, and the two methods it provides are pretty basic. In this instance, the listener just writes the message plus an end of line character to theLogAreaof the basic view we created a moment ago. theLogAreais something that the basic view model provides, so don’t worry about creating one.(lines 65-74) Configuration.
We create a
BasicPluginConfigModelto act as a config page for our plugin. We use the UI manager for this, and give it a parent heading ("plugins", which you should always use for your plugins) and a lookup name for its own name (BASENAME + "name" in this case, but whatever property you want from yourMessages.propertiescan be substituted here) that Azureus will use to place it in the preferences dialog. At this point, in fact, you have created a preference page (albeit a blank one) and it will already appear in the prefs dialog. Next, create the enabled parameter: calladdBooleanParameter2(the 2 is, I assume, for the more modern versions. There is a version w/0 2 on it, but I believe that it is deprecated) with the ID of the name from your Messages file, a key to store the config value under (this MUST be unique — I recommend prefixing it with your plugin name as I have done here, and then making certain that you have no duplicate values for your key names) and the default value, a boolean parameter. After that, create the prefix string parameter: calladdStringParameter2and the parameters are the same asaddBooleanParameter2in general, although a string parameter usinggetLocalisedMessageis what the default calls for. Lastly, make the prefix’s enabled state depend on thebooleanParameteras in line 74.(line 76) Finally, we add this plugin as a
downloadManagerListenerand off we go!
Deployment
Deploying your plugin requires that you create a Jar file containing the classes, the plugin.properties file, and any ancillary files that the plugin requires. I do it by right-clicking on my [src] directory (your mileage will vary on this one depending on where you put the source code — q.v. my earlier comment on project layout) and selecting "Export" from the resulting menu. I then export to Jar with ONLY the [src] directory and nothing above it highlighted, and I place that jar file in [azureus install dir]/plugins/[plugin name], although you can call the latter directory whatever you want. Debugging is, however, not working out for me. If anyone knows of a way to debug plugins in a running instance of Azureus, I’d love to know!
Gotchas
The plugin API for Azureus is not terribly well documented. Generally, though, if you stick to public methods of the pluginInterface and classes from the org.gudy.azureus2.plugins package tree, you’re probably going to be okay. If you get stumped, you can post questions as comments to this page, and I’ll try to answer them. Also, there’s an IRC channel #azureus and the sourceforge plugin developer forum to try. Hopefully this helps!
That’s an outstanding request; I’ll look at it eventually, but it’s a ways off. Work is keeping me pretty slammed these days. Thank you for the interest, please be patient and I’ll get around to it, I promise!
http://www.offby1.net/?dl=autostop-2.0.beta.3.zip link is dead
Auto Categorizer not saving - I have the same issues the guys above have. When I restart, all of my categories are gone.
Found another link http://azureus.sourceforge.net/plugin_details.php?plugin=autostop
Hi Chris
Thanks for this plugin, it’s solved one of Azureus’s few issues; how to seed different types of torrents to different ratios.
I would echo Ambrose and Hal to some extent. I’ve found the right-click per torrent settings, but I’m mystified by the “Autostop Criteria” tab. What does it do, and how can I interact with it? Clicking on the Plus button does nothing.
Other than that, I’m pleased and grateful!
Outstanding plugin concept, well done Chris. Implementation needs some polish but generally I think you have most of what you need in place - keep it simple and focused = good plugin. Here’s my suggestions:
1) add some simple instructions to readme - ie. *tools->options to enable and change global ratio *right click individual torrent to do per torrent settings
2) allow more ratios for the global setting - currently only 1.0, 1.5, 2.0, 5.0, whereas for individual you can enter a custom value.
3) seems that everyone that use this first goes to Tools->Plugins->Autostop which brings up a spreadsheet that absolutely nothing. Remove this from the user experience.
Otherwise well done.
Actually, this isn’t working for me.
I’ve set the global ratio to 1.0, and individual ratios above that, but they are stopping at 1.0, with a Seeding Rank of “Share Ratio OK”.
If I raise the ratio under Queue>Seeding>Ignore Rules, torrents which do have a ratio limit of 1.0 start seeding, despite Autostop’s global ratio telling them they’ve reached their target.
I’m on Vuze 3.1.1.0 on Java 1.6 on Unbuntu 8.04; any help gratefully received.
I don’t really understand how to use this plugin…I have the fields filled out like so: Host: talk.google.com Port: 5222 Name: myemailaddress@gmail.com (is this right?)
User Name: myemailaddress@gmail.com (?) Resource Name: windows_computer Password: mypassword
In the log it says: Unable to perform initial connection to the Jabber service. XMPPError connecting to talk.google.com:5222.
So I’m guessing one of those fields is wrong.
Google is not full-featured jabber server. Read many jabber clients FAQs - there are special description how o connect to GTalk, which features to suspend and which to enforce.
For example, jabber server should support multiple connections with different programs/computers into the single login/password. Can you connect to the same GTalk from two computers at the same time ? If you cannot, AzJabber would seems fail too.
azjabber does, however, work with gmail; it’s gmail that I use it for. Colin, here’s the settings that work:
Host: talk.google.com Port: 5222 Name: gmail.com
User name:
Resource name: anything at all
Password: I can’t tell you that
I admit to being an idiot, but could someone please give me an example of a rule for Auto Categorizer? All I want to do is have torrents categorized based on tracker URL. I’m entering the domain as the tracker URL, and it’s not working.
I’m having the same issues - for some reason it’s managed to save just one category, but after a restart, all others are gone.
never fucking mind, i figured it out. what a pain in the ass.
So the download link is still broken and unfortunately the link:
http://azureus.sourceforge.net/plugin_details.php?plugin=autostop
is two version old and missing the plugin.properties file again. Is there any way to get the most recent beta with the proper properties file?
Heh. A little poking around helps. I noticed the plugin link was broken but based on the other links I guessed:
http://www.offby1.net/plugins/
and lo and behold a directory listing of all plugin versions. However I’m still confused about two things.
First I would have thought that the beta3 was most recent. However it (and the other betas) are all 28 kB. However the one at sourceforge is 36 kB; although the betas are more recently dated.
Also there is still no sign of the plugin.properties file. I am no Azureus plugin expert but something seems missing since the only option on the plugin page are enable and detailed logging which seem sort of like defaults. Where is this file and don’t I need it?
@Colin
In Host configuration Name must be gmail.com and in client configuration “username” is your gmail username, but you don’t have to put “@gmail.com”, for me these settings work well.
Well, it’s still on my livejournal feed. So I, at least, still see it. Ha!
That, and as said prior, good luck.
I’m here, too. congratulations! Hi to your future offspring from me, too
hind sight makes some things sound much different.
Well lad-di-da. I wasn’t expecting this. Paint me tickled! (just go with it. I think by now we all know I like it random and nonsequiturial and that’s just how it’s gonna be)
I love that you are starting this up and in the form of a journal and outlet.
I read this!
Frequent reader who infrequently comments. Feed me weird things, and who knows what’ll come out of me once I’ve digested it.
That was unintentionally scatological, I swear.
Long story short: post. Whether I reply or no, I have read your writings.
I’m still here way in the distance, I may have not looked for a bit but will now.
that was fast!
I think how this is all falling into place is quite impressive. It is impressing upon me how I have watched you relationship evolve in a lovely fashion (and I have only seen near 3 yrs of it) and how gracefully you both have been handling the swiftness of it all. What does it say about me that it freaks me out at the same time? Oh right, that I am holding onto my selfish lifestyle quite happily and afraid my grip may be tenuous.
Yeah, there’s definitely an interesting sense of momentum to this whole thing. It’s sort of been there for me since I found out about the baby; I’ve been swept along in events, with what feels like only marginal control over the direction of travel.
This isn’t to say it’s a bad feeling, but it’s certainly interesting.
I know that from your perspective there’s a lot changing too. Char and I both plan to ensure that the life we have — the friends, the interests, &c — don’t fall victim to the massive change in our family life that is coming. Moving doesn’t make this easier, of course; it takes us out of our current comfortable place, makes it a bit more of an effort to visit us, and more of an effort for us to see some of our friends. We hope that this won’t be too much of a change to work around, but obviously there’s going to be some.
So, not much socializing yet outside of work? You’d have picked up more words I think. What are the women like? We, and Edmonchuk, got some snow since you left, actually a fair bit here.
/_\
change is good
/\ is what I meant
gir. it’s not working. you can erase these if you want.
/\ /__\
If it makes it sound any better, all our friends with kids really stressed how much our lives would change when the wee one arrived but I wouldn’t say it’s to our detriment. I think we get to see our firends more often now that we have Sophie. This is especially true for me, as I am no longer working 70-80 hours per week.
Congratulations Chris and Char and I am very happy that you have restarted this site. I like to keep up with what everyone is up to
Yeah, that would certainly make it easier to have a life, wouldn’t it?
It’ll be a bit different for us, obviously. It’s different for everyone. Char will get to be diurnal if she wants to, which will be a very pleasant state of affairs. Additionally, our good friends have all made clear that they won’t stand for us vanishing from the radar, which means that the biggest concern is pretty much taken care of. I’m still not aggressively looking forward to it, but the things that worried me are, one by one, becoming less of an issue.
It breaks my heart to know this part of you is over, I wish it wouldn’t have worked out like this. Hey, You’re way in front of me on the numbers. I think it is great that you did get to fly over Brazil.
Yeah, it’s pretty hard to take, no mistake. I mean, it is what it is, but I don’t have to like it. Safety über alles, but oh, what a price to pay.
I am with Moi on this one. I know what it meant to you and the fact that you couldn’t get into it with me when it was fresh I understood meant it was pretty emotional. More reason to have coffee Tuesday evening. Let’s dish about you.
It’s a nice place. I can’t wait to see you guys settled in.
How’s the unpacking going?
A shout out echo. You two made it easier on us being uber organized and ready for us. Forking over $150 in beer was a nice touch as well. I can’t wait to warm you house!!!
Oh my god, you’re right. Anyone who’s watched that movie would have quite a different response to that ad than Telus expects, I’m sure. It’s powerful music, just not in the way they want it to be.
Good lord. Even knowing what that was going to be didn’t prepare me for that.
I know!
Make no mistake, I can (and do!) listen to Lux Aeterna without irony purely for the joy of the sound; it’s a beautiful song. But this… this is just… Words fail. They really do.
I don’t know. I like it, and I have a pretty visceral reaction to the song. Sadly, I think Telus did a good job reinventing that. Makes me want a smart phone.
I’ll use the obvious analogy, here:
You want some dope who graduated PhD in Agriculture to irrigate your crops with Gatorade, cause it quenches all thirst? He tried really, really hard in school ….. :/
You want some guy to have to try really really hard to remember a differential diagnosis on your dying mom when he only has 10 minutes to administer treatment or her stroke will permanently ruin her brain?
I didn’t think so.
Yeah, there is more to it than effort, for sure. I really don’t like that this “self-esteem” grading is going on in elementary schools, but to hear that some people think it should be happening in post-secondary education too, gross. In hindsight I guess I shouldn’t be that surprised, those kids who got graded “W” for Wonderful will eventually get to college age.
Damn right you’ll teach your kids a mere sense of entitlement is not what will get you by alone. If your kid’s generation could be the first one in a while that starts to purge this sense of entitlement that has been going on that would be great.
also… this wraps is up http://mwkworks.com/lifesnotfair.html
I always liked Maligne then frozen although I never saw it that often. Char told me a bit about you going up there. Talk to you when you get home, moi
when are you going to post again? I miss you