FTP更新チェッカ

30分プログラム、その92。FTPのログを解析して、ごにょごにょするやつ。
id:mzp:20070723:ftpのバージョンアップ版。

使い方

$ ruby ftup.rb

地味ですね。

コード

#! /opt/local/bin/ruby -w
# -*- mode:ruby; coding:utf-8 -*-
#
# ftup.rb -
#
# Copyright(C) 2007 by mzp
# Author: MIZUNO Hiroki <hiroki1124@gmail.com> 
# http://mzp.sakura.ne.jp/
#
# Timestamp: 2007/07/27 22:42:02
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Ruby itself.
#
require 'kconv'
require 'net/ftp'
require 'time'

class Time
  def same_day?(o)
    self.day == o.day and
      self.month == o.month and
      self.year  == o.year
  end
end

# ------------------------------
# Feeds
# ------------------------------
def file(*filenames)
  filenames.map(&File.method(:open))
end

def ftp_get(host,pass,user,pattern,count=5)
  ftp = Net::FTP.new host,pass,user
  list = ftp.nlst pattern
  list[-count..-1].map{|file|
    STDERR.puts file

    begin
      local = File.basename file
      ftp.getbinaryfile file,local
      File.open local
    rescue Net::FTPTempError
    end
  }
end

# ------------------------------
# Filter
# ------------------------------
Command = Struct.new 'Command',:type,:filename,:mtime
def filezilla(files)
  timestamp = %r!\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}!
  files.map{|io|
    commands = []
    cwd = '/'
    io.each{|line|
      case line.chomp
      when %r!(#{timestamp}).*STOR (.*)\Z!
           commands << Command.new(:up,
                                File.expand_path($2,cwd),
                                Time.parse($1))
      when %r!(#{timestamp}).*RETR (.*)\Z!
        commands << Command.new(:down,
                                File.expand_path($2,cwd),
                                Time.parse($1))
      when %r!"(/.*)" is current directory.\Z!
        cwd = $1
      end
    }
    commands
  }

end

def rank(commands,count=5)
  rank = Hash.new 0
  commands.each{|command|
    rank[command.filename] += 1
  }
  rank.keys.sort{|a,b| b<=>a}[0,count].map{|key|
    [key,rank[key]]
  }
end

def recent(commands,count=5)
  commands.select{|command|
    command.type == :up
  }.reverse[0,count]
end

# ------------------------------
# Publish
# ------------------------------
def ftp_put(host,user,pass,file)
  ftp = Net::FTP.new host,user,pass
  ftp.putbinaryfile file
  ftp.close
end

# ------------------------------
# customize
# ------------------------------
feeds = ftp_get 'example.com','mzp','xxx','/Logs/*.log',7
feeds.delete nil
commands=filezilla feeds

File.open('update.txt','w'){|io|
  c = commands.flatten

  r = ""
  time = nil
  recent(c,20).each{|cmd|
    if time == nil or not time.same_day?(cmd.mtime) then
      r += cmd.mtime.strftime("\n**%Y/%m/%d\n")
    end
    r += "-- #{cmd.filename}\n"
    time = cmd.mtime
  }

  t = rank(c,10).map{|name,count|
    span = ' ' * [50-name.size,0].max
    "- #{name}#{span}#{'=' * count}"
  }.join("\n")

  io.puts <<-UPDATE
* 最近更新されたファイル(20件分)
#{r}

* 最近人気のファイル(Top 10)
#{t}
  UPDATE
}
ftp_put 'example.com','mzp','xxx','update.txt'

メモ

  • 出力の方法や、入力の方法がいろいろある。そこで、各機能を関数で実現して、組合せやすいようにした。
  • だから、なるべく副作用は使わない方向で
  • 続きはまた明日