File sharing with RTMFP

by Fake
RTMFPを使用した、ファイル共有プログラムです。
データの受け渡し部分はP2Pで行われますが、仕組み的にはBitTorrentに近く、送受信側ともに複数クライアントが参加出来ます。
(RTMFPのオブジェクトレプリケーションを使用)
受信側も、既に受信した部分のデータは、送信に協力することになります。
違法なファイルのやり取りはしないでください。

送信側の操作:
[Provide]ボタンを押して、送信したいファイルを選択してください。
テキストボックスに、ファイルのハッシュ値が表示されますので、受信する人に伝えてください。

受信側の操作:
テキストボックスに、受信するファイルのハッシュ値を入力して、[Receive]ボタンを押してください。
受信が完了すると、[Receive]ボタンが[Save]ボタンになり、ファイルを保存出来ます。
♥11 | Line 196 | Modified 2010-11-07 21:06:16 | GPLv3 License
play

ActionScript3 source code

/**
 * Copyright Fake ( http://wonderfl.net/user/Fake )
 * GNU General Public License, v3 ( http://www.gnu.org/licenses/quick-guide-gplv3.html )
 * Downloaded from: http://wonderfl.net/c/rCkr
 */

<?xml version="1.0" encoding="utf-8"?>
<!--
I provide this code as GPL;
ライセンスはGPLです。
-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onInitApp()">
<mx:Button id="btnProvide" label="Provide" x="30" y="50" />
<mx:Button id="btnReceive" label="Receive" x="30" y="150" />
<mx:Script>
<![CDATA[
    import flash.events.ProgressEvent;
    import flash.events.MouseEvent;
    import flash.events.Event;
    import flash.net.FileReference;
    import flash.display.Sprite;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextField;
    import com.adobe.crypto.SHA1;
    import flash.events.NetStatusEvent;
    import flash.net.*;
    
    private var debugout:TextField;
    private var txtHash:TextField;
    private var filename:String;
    private var filesize:uint;
    private var filehash:String;
    private var chunknum:uint;
    private var isProvide:Boolean;
    private var flgary:Array;
    private var filedata:ByteArray;
    private var recnum:uint;
    
    private var gs:GroupSpecifier;
    private var nc:NetConnection;
    private var group:NetGroup;
    
    private var CHUNK_SIZE:uint = 65536;

    private function onInitApp():void {
        // init debugout
        debugout = new TextField();
        debugout.x = 250;
        debugout.y = 50;
        debugout.autoSize = TextFieldAutoSize.LEFT;
        debugout.background = true;
        debugout.border = true;
        debugout.backgroundColor = 0x000000;
        debugout.textColor = 0x0000FF;
        //parent.addChild(debugout);

        txtHash = new TextField();
        txtHash.x = 30;
        txtHash.y = 100;
        txtHash.autoSize = TextFieldAutoSize.LEFT;
        txtHash.background = true;
        txtHash.border = true;
        txtHash.backgroundColor = 0xFFFFFF;
        txtHash.textColor = 0x000000;
        txtHash.restrict = "a-f0-9";
        txtHash.type = TextFieldType.INPUT;
        parent.addChild(txtHash);
        txtHash.text = "Paste a file hash to receive.";
        
        filedata = new ByteArray();
        
        btnProvide.addEventListener(MouseEvent.CLICK, onProvideClicked);
        btnReceive.addEventListener(MouseEvent.CLICK, onReceiveClicked);
    }
    
    private function onProvideClicked(e:MouseEvent):void {
        var file:FileReference = new FileReference();
        file.addEventListener(Event.SELECT, function (e:Event):void {
            filename = e.target.name;
            txtHash.text = "now loading";
            e.target.load();
        });
        file.addEventListener(ProgressEvent.PROGRESS, function (e:ProgressEvent):void {
            txtHash.appendText(".");
        });
        file.addEventListener(Event.COMPLETE, onFileLoaded);
        file.browse();
        btnProvide.enabled = false;
        btnReceive.enabled = false;
        txtHash.type = TextFieldType.DYNAMIC;
    }
    
    private function onReceiveClicked(e:MouseEvent):void {
        isProvide = false;
        filehash = txtHash.text;
        txtHash.type = TextFieldType.DYNAMIC;
        btnProvide.enabled = false;
        btnReceive.enabled = false;
        
        e.target.removeEventListener(e.type, onReceiveClicked);
        
        initRTMFP(filehash);
    }
    
    private function onSaveClicked(e:MouseEvent):void {
        var file:FileReference = new FileReference();
        file.save(filedata, filename);
    }
    
    private function initRTMFP(hash:String):void {
        debugout.appendText(hash + "\n");
        // init rtmfp group spec
        gs = new GroupSpecifier("net.wonderfl.fake.p2p.fileshare.test:" + hash);
        gs.postingEnabled = true;
        gs.ipMulticastMemberUpdatesEnabled = true;
        gs.multicastEnabled = true;
        gs.objectReplicationEnabled = true;
        gs.routingEnabled = true;
        gs.serverChannelEnabled = true;
        // init rtmfp connection
        nc = new NetConnection();
        nc.addEventListener(NetStatusEvent.NET_STATUS, procNetStatus);
        nc.connect("rtmfp://p2p.rtmfp.net/445d9931dfac67103408ae5f-ad98e28e2386/");
    }
    
    private function onFileLoaded(e:Event):void {
        filehash = SHA1.hashBytes(e.target.data);
        txtHash.text = filehash;
        filesize = e.target.size;
        isProvide = true;
        filedata.writeBytes(e.target.data)

        initRTMFP(filehash);
    }
    
    private function initFileSharing():void {
        chunknum = Math.ceil(filesize / CHUNK_SIZE);
        group.addHaveObjects(0, chunknum);
        
        // init flag array
        flgary = new Array();
        for (var i:int = 0; i < chunknum; ++i) {
            flgary.push(true);
        }
    }
    
    private function initFileReceiving():void {
        group.addWantObjects(0, 0);
        recnum = 0;
    }
    
    private function procNetStatus(e:NetStatusEvent):void {
        debugout.appendText(e.info.code + "\n");
        switch (e.info.code) {
            case "NetConnection.Connect.Success":
            try {
                group = new NetGroup(nc, gs.groupspecWithAuthorizations());
                group.addEventListener(NetStatusEvent.NET_STATUS, procNetStatus);
                group.replicationStrategy = NetGroupReplicationStrategy.RAREST_FIRST;
            } catch (e:Error) {
                debugout.appendText(e.toString() + "\n");
            }
                break;
            case "NetGroup.Connect.Success":
                isProvide ? initFileSharing() : initFileReceiving();
                break;
            case "NetGroup.Neighbor.Connect":
                break;
            case "NetGroup.Posting.Notify":
                break;
            case "NetGroup.Replication.Request":
                if (e.info.index == 0) {
                    group.writeRequestedObject(e.info.requestID, {
                        filename: filename,
                        filesize: filesize,
                        filehash: filehash,
                        chunknum: chunknum
                    });
                } else if (e.info.index <= chunknum) {
                    var b:ByteArray = new ByteArray();
                    filedata.position = (e.info.index - 1) * CHUNK_SIZE;
                    var rsize:uint = filesize - filedata.position;
                    if (rsize > CHUNK_SIZE) {
                        rsize = CHUNK_SIZE;
                    }
                    filedata.readBytes(b, 0, rsize);
                    group.writeRequestedObject(e.info.requestID, {
                        data: b
                    });
                } else {
                    // indexが範囲外
                    group.denyRequestedObject(e.info.requestID);
                }
                break;
            case "NetGroup.Replication.Fetch.Result":
                var obj:Object = e.info.object;
                if (e.info.index == 0) {
                    filename = obj.filename;
                    filesize = obj.filesize;
                    filehash = obj.filehash;
                    chunknum = obj.chunknum;
                    group.addWantObjects(1, chunknum);
                    // init flag array
                    flgary = new Array();
                    for (var i:int = 0; i < chunknum; ++i) {
                        flgary.push(false);
                    }
                } else if (e.info.index <= chunknum) {
                    debugout.appendText(e.info.index.toString + "\n");
                    filedata.position = (e.info.index - 1) * CHUNK_SIZE;
                    filedata.writeBytes(obj.data);
                    flgary[e.info.index-1] = true;
                    group.addHaveObjects(e.info.index, e.info.index);
                    ++recnum;
                } else {
                    // indexが範囲外
                }
                txtHash.text = recnum.toString() + "/" + chunknum.toString();
                if (flgary.indexOf(false) === -1) {
                    // 全ピースを受け取り完了
                    btnReceive.enabled = true;
                    btnReceive.label = "Save";
                    btnReceive.addEventListener(MouseEvent.CLICK, onSaveClicked);
                }
                break;
        }
    }
 ]]>
</mx:Script>
</mx:Application>