Monday, July 12, 2010

Info - File Storage

If you've used a Bittorrent client, or maybe have installed many games using Steam, you'll notice that files are preallocated space before the download begins. This is different from your average HTTP (web browser) download, though many HTTP clients could do this - there's just less reason for it.
With Bittorrent the download will tend to occur by acquiring pieces of files not in sequential order. This isn't always the case, and some extensions to Bittorrent may mean it's easier to stream the file in the original order. Regardless, it's the norm for the files to be created and then parts overwritten as the file's real contents are received.

Preallocating
My notes reference DownThemAll for when the preallocation code was written - though pre-allocation in downThemAll is different, the general approach is the same: use the nsISeekableStream to get to the position where the last byte would be.

Inserting/Extracting
MDC's guide to writing binary data is excellent, though I've previously had trouble with the suggested file permissions (resulting in (NS_ERROR_FILE_ACCESS_DENIED) [nsIFileOutputStream.init] ). I've reached success through trial and error, though I should probably revisit this at sometime.
The extract method has the form extract(in nsILocalFile baseFolder, in nsIVariant pathArray, in long offset, in long datalength) returning an ACString. Path arrays in Bittorrent have been discussed previously; they represent the path to the file by splitting the path into several strings instead of relying on a OS dependent directory delimiter. The BaseFolder parameter allows an easy way of setting the location where the torrent will be downloaded without having to alter every pathArray.
I still use nsIBinaryInputStream with the readBytes function, which is why the ACString type is being used.

File Storage
So yes, all of this focuses on storing the data in files. It doesn't have to be that way, a torrent can be held completely in memory for other uses... I hope to be able to write about some fun with Bittorrent that is less concerned with just implementing the protocol. Here's Brad Goldsmith's CompTorrent for some interesting use of bittorrent. He has since submitted his PhD thesis, so there is further reading if you're interested.

Anyway, onto files:
Implementation of FileStorage XPCOM component
IDL for FileStorage XPCOM component

Examples:

Usage:

var service = Components.
classes["@wikiscraps.com/storage/file;1"].
getService(Components.interfaces.btIBittorrentFileStorage);
var baseFolder = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("TmpD", Components.interfaces.nsILocalFile);
baseFolder.appendRelativePath("test");
// We'll create two files, one in a subfolder
var list = [{path:["a.txt"],size:10},{path:["a","a.txt"],size:10}];
service.preallocate(baseFolder,list);
service.insert(baseFolder, ["a.txt"], 6, "cdef");
service.insert(baseFolder, ["a.txt"], 4, "abcd");
var val = service.extract(baseFolder, ["a.txt"], 4, 6); // returns "abcdef"


Usage with FileInfo:
var infoService = Components.
classes["@wikiscraps.com/fileinfo;1"].
getService(Components.interfaces.btIBittorrentFileInfo);
var storageService = Components.
classes["@wikiscraps.com/storage/file;1"].
getService(Components.interfaces.btIBittorrentFileStorage);
var baseFolder = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("TmpD", Components.interfaces.nsILocalFile);
baseFolder.appendRelativePath("test");
// Fake block
var fakeBlock = "Baaaaaaaaaaa";
var fakeBlockOffset = 5;
// Fake info
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;

// Okay, we've faked the state, now
// let's try to save the block to files!

var fileList = infoService.listFile(fakeInfo);
var fileParts = infoService.blockToFileParts(fakeInfo, fakeBlockOffset, fakeBlock.length);
var completedIndex = 0;
for (var i=0; fileParts.length > i; i++) {
storageService.insert(
baseFolder,
fileList[fileParts[i].index],
fileParts[i].offset,
fakeBlock.substr(completedIndex,fileParts[i].size));
completedIndex += fileParts[i].size;
}


More information on the .torrent structure (Theory.org) Bittorrent Protocol Specification (BEP0003)
The bencoding XPCOM component.
The bittorrent tracker XPCOM component.
The Bittorrent File Info XPCOM component.

No comments:

Post a Comment