twistedを使ってFTPサーバを立てる - Kivy Advent Calendar 2013

PCとAndroid端末とのファイルのやり取りには、USBつなぐのも面倒なのでDroidOverWifi (http://www.droidoverwifi.com/) なんかを使っています。いわばファイル編集機能特化のHTTPサーバです。
でもKivyLauncherだってtwistedが入っているんだし、それを使ってFTPサーバ作れないかなーと思って作ったのがこれです。
実行すると「IPアドレス:ポート番号」を表示してFTPサーバが立ち上り、FTPクライアントを使ってファイルのアップロード/ダウンロードができます(ポート番号は8021、PASVモードはオンにしてください)。
FFFTP (http://sourceforge.jp/projects/ffftp/) みたいなフォルダごと転送できるクライアントを使うと結構便利に使えます。


(/sdcard/kivy/ftpserver/)

android.txt お約束
ipaddr.py IPアドレスを表示させたいだけで作ったモジュール
main.py スクリプト本体
passwd.dat パスワードファイル みちゃだめ

(ipaddr.py)

import socket
import fcntl
import struct

def get_ipaddr():
    interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    for iface in interfaces:
        ifreq = struct.pack('32s', iface)
        try:
            result = fcntl.ioctl(s.fileno(), 0x8915, ifreq)
            return socket.inet_ntoa(result[20:24])
        except IOError:
            continue
    return None

(main.py)

from kivy import kivy_home_dir
from kivy.app import App
from kivy.uix.label import Label
from kivy.support import install_twisted_reactor
install_twisted_reactor()

from twisted.protocols.ftp import FTPFactory, FTPRealm, FTPShell, IFTPShell
from twisted.cred.portal import Portal
from twisted.cred.checkers import AllowAnonymousAccess, FilePasswordDB, ANONYMOUS
from twisted.internet import reactor
from twisted.python.filepath import FilePath
import ipaddr

class MyFTPRealm(FTPRealm):

    HOME = kivy_home_dir
    #HOME = '/sdcard/kivy/'

    def requestAvatar(self, avatarId, mind, *interfaces):
        for iface in interfaces:
            if iface is IFTPShell:
                if avatarId is ANONYMOUS:
                    avatar = FTPAnonymousShell(self.anonymousRoot)
                else:
                    avatar = FTPShell(FilePath(self.HOME))
                return IFTPShell, avatar, getattr(avatar, 'logout', lambda: None)
        raise NotImplementedError("Only IFTPShell interface is supported by this realm")

class FTPServerApp(App):

    PORT = 8021

    def build(self):
        portal = Portal(
            MyFTPRealm("./"),
            [AllowAnonymousAccess(), FilePasswordDB("passwd.dat")])
        factory = FTPFactory(portal)
        reactor.listenTCP(self.PORT, factory)

        self.label = Label(text="FTP server started\n%s:%d" % (ipaddr.get_ipaddr(),self.PORT))
        return self.label

if __name__ == '__main__':
    FTPServerApp().run()

解説

最初にinstall_twisted_reactor()を実行すれば、Kivyアプリの開始・終了のタイミングでtwistedリアクタの開始・終了を行うようになります。
後はTwistedのプログラミングをすればOK。protocolクラスを使ってFTPサーバを組み立てていくわけですが、デフォルトのFTPRealmは「/home/(ユーザ名)」の下を公開しようとします。Androidはそんなディレクトリないので、サーバは立ち上がるけど認証後は550エラーを返し続けることになります。ここではMyFTPRealmを作って該当のメソッドを置き換えています。
FTPサーバで公開されるディレクトリ (kivy_home_dir) ですが、Androidだと「/sdcard/kivy/(アプリ名)/.kivy/」の下になります。ここはKivyのログファイルなどが書き込まれる場所なので無難といえば無難です。
とはいえ後でファイル移動させるのも面倒だし、どーせ自分しかLANにいませんよというなら「/sdcard/kivy/」や「/sdcard/」の下をどーんと公開しちゃうのもありかもしれません。
(何が起こっても一切責任持ちませんけど...)