uTrackMe 向けサーバスクリプト

先日,一人で勝手に騒いでいたiPhone/iPodTouch向けアプリ,uTrackMe ですが,あまりにも騒ぎすぎた結果可読性を損なっているので,書き直し&アップデートします.
uTrackMe はHTTP POST リクエストで任意の Web サーバに対して uTrackMe アプリを起動してから現在までのトラックをまとめて POST する DIY チックなアプリです.GPSed と違うところは,

  • WiFiや3Gが切れても,かまわず HTTP POST を繰り返す,
  • 任意のサーバに対して POST できる,
  • POSTできるトラック情報が多い(フォーマット参照).過去のポイントどころか,速度や向きも出力可能,
  • サーバスクリプトが規定フォーマット(plist あるいは XML)にて,他ユーザのトラックログを返すときは,当該他ユーザの位置情報もアプリ上に表示できる.

ただし,

  • サーバスクリプトは自分で書け.

という非常に男らしいアプリです.

というわけで,サーバスクリプトを ruby で書きました−.ぼくの rubyruby じゃないそうなので,とても汚いソースだ.有志の方々は是非作り直してぼくにおめぐみください.

下記のスクリプトcgiとして動作するようにWebサーバに設置し,当該cgiファイルに chmod 777 な data ディレクトリを作成してください.あとは,当該 cgi に uTrackMe から POST するだけ.

#!/usr/bin/ruby
print "Content-type: text/html\n\n"

require "cgi"
require "csv"
require "digest/md5"

cgi = CGI.new

def check_format(cgi, n)
  param_list = [
                "id",
                "label",
                "delta",
                "lock",
                "time[#{n}]",
                "lat[#{n}]", 
                "lon[#{n}]", 
                "acc[#{n}]", 
                "spd[#{n}]", 
                "dir[#{n}]",
                "alt[#{n}]"
               ]
  param_list.each do |p|
    return false if cgi[p].empty?
  end

  return false if cgi["time[#{n}]"].to_i < 1274713660

  return true
end

def get_str(cgi, n)
  param_list = [
                "label",
                "delta",
                "lock",
                "time[#{n}]",
                "lat[#{n}]", 
                "lon[#{n}]", 
                "acc[#{n}]", 
                "spd[#{n}]", 
                "dir[#{n}]",
                "alt[#{n}]"
               ]
  str = Time.at(cgi["time[#{n}]"].to_i).strftime("%Y/%m/%d-%H:%M:%S").to_s + "," +  Digest::MD5.hexdigest(cgi["id"]) + "," + param_list.map{|p| cgi[p]}.join(",")
  return str
end

if cgi["label"] != "" then
  i = 0
  while i < 86400
    if cgi["time[#{i}]"] == ""
      n = i
      break
    end
    i += 1
  end
  n -= 1

  t = cgi["time[#{n}]"].to_i
  time = Time.at(t)

  if check_format(cgi, n) != true
    print "[Err] Got string now was invalid.\n"
    exit
  end

  str = get_str(cgi, n)

  if File.exist?("./data/" + Time.at(t).strftime("%Y%m%d").to_s) == true
    fn = "./data/" + Time.at(t).strftime("%Y%m%d").to_s
  else
    if File.exist?("./data/" + Time.at(t-1).strftime("%Y%m%d").to_s) == true
      fn = "./data/" + Time.at(t-1).strftime("%Y%m%d").to_s
    end
    fn = nil
  end
  
  if fn != nil
    f = open(fn, "r")
    last = f.gets
    last = f.gets
    if last != nil
      f.seek(-2, IO::SEEK_END)
      until (c = f.getc) == ?\n || c == ?\r
        f.seek(-2, IO::SEEK_CUR)
      end 
      last = f.gets
    else
      f.rewind
      last = f.gets
    end
    f.close
    last_t = CSV.parse(last)[0][5]
  else
    last_t = "0"
  end

  i = 0
  forgotten_num = 0
  last_put_time = 0
  while i < 86400 && cgi["time[#{i}]"].to_i < t
    if cgi["time[#{i}]"].to_i > last_t.to_i + cgi["delta"].to_i
      forgotten_str = get_str(cgi, i)
      if cgi["time[#{i}]"].to_i - last_put_time > cgi["delta"].to_i
        File.open("./data/" + Time.at(cgi["time[#{i}]"].to_i).strftime("%Y%m%d").to_s, "a"){|f|
          f.write forgotten_str + "\n"
        }
        last_put_time = cgi["time[#{i}]"].to_i
        forgotten_num += 1
      end
    end
    i += 1
  end

  File.open("./data/" + time.strftime("%Y%m%d").to_s, "a"){|f|
    f.write str + "\n"
  }
  
else
  str = "Null"
end

if forgotten_num > 0
  print forgotten_num.to_s + " point(s) not completed just have been uploaded!"
  File.open("./data/error", "a"){|f|
    f.write Time.now.to_s + " " + "[" + cgi["label"] + "]" + forgotten_num.to_s + " point(s) not completed just have been uploaded!" + "\n"
  }
else
  print "[Succ] " + str
end

サーバ側に残るログは下記のフォーマットとなります.

md5(id), time, label, delta, lock, unix-epoch-time, lat, lon, acc, spd, dir, alt

基本的に,uTrackMeからのPOSTフォーマットを踏襲していますが,下記の修正を加えております.

  • md5(id): idは端末固有IDなので,個人情報保護の観点から念のためMD5ハッシュしています.SHA1は若干重いので,MD5で妥協.
  • time: uTrackMeのtimeはUNIX-Epochですが,ここではYYYY/MM/DD-hh:mm:ssで出力しています.
  • unix-epoch-time: uTrackMeのtimeを出力.

あと,捕捉ですが,lock(location lock) は,GPS測位できていれば1,失敗しているときは0を渡すようです.ただし,uTrackMe が foreground 動作しているときに限る.

まー,いろいろとまだバグが潜んでいますが,まずは,こいつで遊んでみたいと思います.
ちなみに,uTrackMe はたまに嘘情報を POST してくる.時刻が 0 になっていたり,パラメタがからっぽだったりすることも.気をつけよう.

複数台対応

複数台対応しました.
が,いかんせんソースが汚いので,また.とりあえず,他のiPodTouchがどこにいるかを併せて表示できるようになりました.

だめだな.DBを単一のテキストファイルとして,複数台端末ログの時系列管理は面倒くさすぎる.複数台が単一のテキストファイルを参照/書き込みするために,ロックが発生したり,まとめてGPSトラックを アップロードされたりすると,DBの時系列が狂う.
MySQL かなにかでログ管理するようにします.