Wednesday, July 7, 2010

Liking getService over createInstance (plus some xpcshell unit tests for bittorrent extension)

So, I've finished the tracker code as a service, instead of creating an object for each tracker we communicate with. I like it. Next, I'm writing a service to simplify interpreting the info section of a .torrent file (once this data has been bedecoded). Actually, the info section is really easy to understand, as seen in Theory.org's Bittorrent Protocol documentation. It's just that the common things I want to know are not always easy to infer directly from the info section.

If we receive a block 100 bytes long that belongs at offset 3,000 in the torrent, what part of which files does this represent? I like to have a function that given the offset and block length will convert this to an array of objects each containing the filename, offset in the file, and how many bytes to write. You know? Something like blockToFileParts(decodedInfo,offset,blockLength) returning [{file:...,offset:...,size:...},...]. Now I can start taking chunks from the received block and placing them in the correct places in files.

Sooo, that's what's next. I've been testing it with a bunch of unit tests, which have proven quite valuable. I've caught several bugs in a controlled situation, which is much easier than debugging using a network sniffer and the good old pen and paper calculator. Here's a peak at the tests, I'll see about dropping the component tomorrow. It's obviously very much a specialised utility class, it doesn't appear obviously useful to anyone but myself at this time.


function fileInfoTests(){
do_check_eq("Now testing fileInfo", "Now testing fileInfo");
// Check class loaded
do_check_true("@wikiscraps.com/fileinfo;1" in Components.classes);
// Check interface exists
do_check_true("btIBittorrentFileInfo" in Components.interfaces);
// Grab the service object.
var service = Components.
classes["@wikiscraps.com/fileinfo;1"].
getService(Components.interfaces.btIBittorrentFileInfo);
var fakeInfo = {
files : [
{ length : 10, path : ["a"] },
{ length : 10, path : ["b"] },
{ length : 10, path : ["c"] },
{ length : 10, path : ["d"] },
{ length : 10, path : ["e"] }
]
};
// Add piece length later, because it has a space in it's key.
fakeInfo["piece length"] = 6;
// Check service methods
// Check the list files/file count/file size/file in torrent/find file
var fileList = service.listFiles(fakeInfo);
do_check_eq(fileList.length,5);
do_check_eq(service.fileCount(fakeInfo),5);
do_check_eq(service.fileSize(fakeInfo, 0),10);
do_check_true(service.fileInTorrent(fakeInfo, ["a"]));
do_check_false(service.fileInTorrent(fakeInfo, ["a","a"]));
do_check_false(service.fileInTorrent(fakeInfo, ["z"]));
do_check_eq(service.findFile(fakeInfo, ["a"]),0);
do_check_eq(service.findFile(fakeInfo, ["a","a"]),-1);
do_check_eq(service.findFile(fakeInfo, ["z"]),-1);
// Check the blockToFileParts method
// For a block spanning all of one file
var fileParts = service.blockToFileParts(fakeInfo, 0, 10);
do_check_eq(fileParts.length, 1);
do_check_eq(fileParts[0].index, 0);
do_check_eq(fileParts[0].offset, 0);
do_check_eq(fileParts[0].size, 10);
// For a block spanning two files
fileParts = service.blockToFileParts(fakeInfo, 0, 11);
do_check_eq(fileParts.length, 2);
do_check_eq(fileParts[0].index, 0);
do_check_eq(fileParts[0].offset, 0);
do_check_eq(fileParts[0].size, 10);
do_check_eq(fileParts[1].index, 1);
do_check_eq(fileParts[1].offset, 0);
do_check_eq(fileParts[1].size, 1);
// For a block of one byte near the beginning of a file
fileParts = service.blockToFileParts(fakeInfo, 10, 1);
do_check_eq(fileParts.length, 1);
do_check_eq(fileParts[0].index, 1);
do_check_eq(fileParts[0].offset, 0);
do_check_eq(fileParts[0].size, 1);
// For a block of one byte near the end of a file
fileParts = service.blockToFileParts(fakeInfo, 9, 1);
do_check_eq(fileParts.length, 1);
do_check_eq(fileParts[0].index, 0);
do_check_eq(fileParts[0].offset, 9);
do_check_eq(fileParts[0].size, 1);

// PieceList check
// With a pieceLength of 6 & file sizes of 10:
// File 0 contains piece 0 & 1 (4 bytes)
// File 1 contains pieces 1 (2 bytes), 2, 3 (2 bytes)
// File 2 contains pieces 3 (4 bytes), 4
// File 3 contains pieces 5, 6 (4 bytes)
// File 4 contains pieces 6 (2 bytes), 7, 8 (2 bytes, irregular sized piece)
var pieceList = service.piecesInFile(fakeInfo, 0);
do_check_eq(pieceList.length, 2);
do_check_eq(pieceList[0], 0);
do_check_eq(pieceList[1], 1);
pieceList = service.piecesInFile(fakeInfo, 1);
do_check_eq(pieceList.length, 3);
do_check_eq(pieceList[0], 1);
do_check_eq(pieceList[1], 2);
do_check_eq(pieceList[2], 3);
/* check pieceIndex 4 (5th piece) is correctly allocated to fileIndex 2 (3rd file) */
pieceList = service.piecesInFile(fakeInfo, 2);
do_check_eq(pieceList.length, 2);
do_check_eq(pieceList[1], 4);
pieceList = service.piecesInFile(fakeInfo, 3);
do_check_eq(pieceList.length, 2);
do_check_eq(pieceList[0], 5);
// Tests missing:
// when piece is shared between 3 files (piecesize > middle file size)
// Files at end of torrent
}

Hmm. Okay, so a few of those methods aren't clear without understanding what the parameters mean. Here's the IDL:


[scriptable, uuid(463206c3-74dd-44f4-b6bf-dc8fbc198993)]
interface btIBittorrentFileInfo : nsISupports
{
nsIVariant listFiles(in nsIVariant decodedInfo);
long fileCount(in nsIVariant decodedInfo);
long fileSize(in nsIVariant decodedInfo, in long fileIndex);
boolean fileInTorrent(in nsIVariant decodedInfo, in nsIVariant filePathArray);
long findFile(in nsIVariant decodedInfo, in nsIVariant filePathArray);
nsIVariant blockToFileParts(in nsIVariant decodedInfo, in long offset, in long blockLength);
nsIVariant piecesInFile(in nsIVariant decodedInfo, in long fileIndex);
};


This again isn't completely useful, because I've used nsIVariant where arrays or objects would be. Ugh... I can see documentation would be shorter way to enlightenment.

No comments:

Post a Comment