FTP 自動アップローダー

【追記】 Bitbucket にリポジトリとしておきました。 いい感じに手直ししていただけると嬉しいです。 yosida95 / AutoFTPUploader / overview — Bitbucket

任意のディレクトリを監視し、ディレクトリの中にあるファイルと FTP で接続したサーバーとを同期するものを作ってみました。 俺得です。 とりあえず同期出来ればいいや、てきな考えなので完成度はべらぼうに低いです。

ローカルディレクトリ内のファイルが上書きされればサーバー上のファイルも上書きし、ローカルディレクトリ内にファイルが追加されればそれをサーバーにアップロードし、ローカルディレクトリ内のファイルが削除されればサーバー上のファイルも削除します。

なお、ディレクトリのネストには対応していませんので、子ディレクトリも監視するときは、別プロセスで対応してください。

#!/usr/bin/env python
#-*-coding: utf-8-*-

import signal
import time
import sys
import os
from ftplib import FTP, error_temp


class Upload:
    def __init__(self):
        self.server = raw_input(u'FTP Server: ')
        self.port = raw_input(u'Port: ')
        self.user = raw_input(u'User: ')
        self.passwd = raw_input(u'Password: ')
        self.pasv = raw_input(u'PASV?(yes/no): ')
        self.pasv = True if self.pasv == 'yes' else False
        self.remote_path = raw_input(u'Remote Path: ')
        self.ascii_exts = [
          '.txt',
          '.log',
          '.htm',
          '.html',
          '.xhtml',
          '.css',
          '.js',
          '.cgi',
          '.py',
          '.pl',
          '.php'
        ]
        self._queue = {'new': [], 'update': [], 'deleted': []}
        self.__connect()

    def __connect(self):
        self.ftp = FTP()
        self.ftp.set_debuglevel(0)
        try:
            self.ftp.connect(self.server, self.port)
            self.ftp.login(self.user, self.passwd)
            self.ftp.set_pasv(self.pasv)
            self.ftp.cwd(self.remote_path)
            print u'Connected'
        except:
            print u'Failed to connect'
            exit(1)

    def _run(self):
        for x in self._queue['new']:
            file = os.path.join(sys.argv[1], x)
            try:
                self._files[x] = {
                  'name': file,
                  'mtime': os.path.getmtime(file)
                }
                self.__upload(x)
            except:
                continue

        for x in self._queue['update']:
            file = os.path.join(sys.argv[1], x)
            try:
                self._files[x]['mtime'] = os.path.getmtime(file)
                self.__upload(x)
            except:
                continue

        for x in self._queue['deleted']:
            self.__delete(x)
            del self._files[x]

        self._queue = {'new': [], 'update': [], 'deleted': []}
        return True

    def __upload(self, file):
        (root, ext) = os.path.splitext(file)
        try:
            fr = open(self._files[file]['name'], 'r')
            try:
                if ext in self.ascii_exts:
                    self.ftp.storlines('STOR %s' % file, fr)
                else:
                    self.ftp.storbinary('STOR %s' % file, fr)
            except error_temp:
                self.__connect()
                if ext in self.ascii_exts:
                    self.ftp.storlines('STOR %s' % file, fr)
                else:
                    self.ftp.storbinary('STOR %s' % file, fr)
            print u'Uploaded: %s' % file
            return True
        except:
            print u'Failed to upload: %s' % file
            return False

    def __delete(self, file, retry=True):
        try:
            try:
                self.ftp.delete(file)
            except error_temp:
                self.__connect()
                self.ftp.delete(file)
            print 'Deleted: %s' % file
            return True
        except:
            print 'Failed to delete: %s' % file
            return False


class Monitoring(Upload):
    def __init__(self):
        Upload.__init__(self)
        self._files = {}
        self.__polling()

    def __get_delta(self):
        now = os.listdir(sys.argv[1])
        before = self._files.keys()
        for x in now:
            if not x in before:
                self._queue['new'].append(x)
        for x in before:
            if not x in now:
                self._queue['deleted'].append(x)

    def __polling(self):
        while True:
            self.__get_delta()
            for x in self._files:
                try:
                    if self._files[x]['mtime'] < os.path.getmtime(self._files[x]['name']):
                        self._queue['update'].append(x)
                except:
                    continue

            if not self._run():
                print('Error!');
                exit(1);

            time.sleep(1)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    if len(sys.argv) == 2 and os.path.isdir(sys.argv[1]):
        process = Monitoring()
    else:
        print "Please pass me filepath as argument."

また、接続する FTP サーバーがいつも一緒なのであれば、該当する raw_input を書き換えて静的に指定してやればいいと思います。

ascii_exts は ASCII 転送モードで転送するファイルの拡張子を詰めたリストです。 必要に応じて追加や削除をしてやってください。