My/Ham/Log まとめ(4)

いつの間にか林檎の花が落ち(10日間ぐらいは楽しめたけどね)、りんごの赤ちゃんが宿っていた。 花が有った根元の部分がふっくらと膨らんで、それを守るように産毛が生えていた。 緑のゆりかごの中で、約半年は成長を続けるのだ。暑い最中は、風水とか日焼けや悪い虫が付かないように 産着を着せてもらうのかな。産着を着せるのは、おじいちゃんやおばあちゃんの仕事だったりして。 これもそれも、かわいい孫のためですもん。

林檎さんの所でお目出度の声を聞いて、葡萄さんも黙ってはいない。こちらはいつ花が咲いて受精したのか わからないけど、しっかり赤ちゃんの兆候が出ていた。勿論、まだ房々とは到底行かないけれど、海ぶどう をギューと小さくしたような身なりになっている。そう、粒々一杯という感じ。りっぱなクラスターに 育つまで、しばし待たれい。

海ぶどうが出てきたけど、海が無くても、外見は雲丹そっくりなのがあるよ。この間、栗畑を見つけたので どんな風になってるか、散歩がてら見に行ってみるかな。あくまで見に行くだけです。女房は子供の頃、 よく拾いに行ったそうですけど。

と言う事で、収穫はまだまだ先になりそうだけど、そろそろ収穫の時期を迎えるものがある。梅だ。 こちらは、樹にたわわに実っていた。余り早く収穫しちゃうと、青酸中毒でお陀仏になっちゃうんだっけ? じっくり実ってから、焼酎と併せればいいんだな。

そして忘れてならないのが桃子さん。成長著しい。林檎さん達とスタートはほぼ一緒だと思うんだけど 、もうふっくら赤くなってる。そのうちにピーチピーチと熟れてくるんだろうね。熟れたらすぐに売らない と傷物になっちゃうから、育ての親は大変だ。ああ、育ての親と言えば、りっぱに育つように器量の悪い子 は間引きしちゃうのね。こうして丹精込めて育てるから、高く売らないと親はやって行けない。人間世界の 一人っ子政策に通じるようで、ちと複雑な心境。

log

このシリーズの2回目で、CRUD(作成、参照、更新、削除)のうち、CRの初歩をやった。今回はこれを 発展させて行こうと思う。んだけど、ログに更新と削除なんて本当に必要なんだろうか?

もしどうしても、更新、削除をやる必要が有るのなら、それはもう、sqlite3コマンドを叩け と言う 事にしよう。何たって、CUI人間ですから。。。

そうすると、参照を充実したいな。ぐぐる様にはかなわないけど、複数項目のand検索とか出来たらいいな。 でも、検索条件によっては非常にたくさんヒットしちゃうかもしれんし。それを全部表示する必要も有る んか?

よって、本当のキーになるコールサインだけの検索で十分だろう。聞こえた局が未交信局か、既に交信済み なら、その時の状況が分かれば良い、、ぐらいで、十分だろう。

AJAXとサーバーサイドを使って、リアルタイム検索も考えてみたけど、清貧なブラウザー w3mでは、Javascriptを 扱えないので、オーソドックスにボタンを付けて検索をする事にした。

こうして、すっきり版を作っておけば、後は気分次第で増設してけばいいんだから。最初からゴテゴテに しちゃうと、後が大変って、何回も経験してますよ。

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

import cgi
import cgitb; cgitb.enable()
import sqlite3
import conf

def pat(cs):
    rv = ''
    for c in cs:
        if c.isalpha():
            rv += 'a'
        elif c.isdigit():
            rv += 'N'
        else:
            rv += c
    return rv

print( conf.HEAD.format(conf.YOUR_CALL) )
print( conf.BODY )

con = sqlite3.connect(conf.YOUR_CALL + ".db")

con.executescript("""CREATE TABLE IF NOT EXISTS qsolog(
utc      timestamp,
callsign TEXT,
rcvd     TEXT,
send     TEXT,
band     TEXT,
mode     TEXT,
comment  TEXT);""")

form = cgi.FieldStorage()
callsign = ''
if form.has_key('callsign'):
    callsign = unicode(form['callsign'].value.upper(),"utf-8")
callsign = callsign if pat(callsign) in conf.PATTERN else ''

if form.has_key('reg') and form.has_key('rcvd') and form.has_key('send') and len(callsign) > 0:
    rcvd = unicode(form['rcvd'].value,"utf-8")
    send = unicode(form['send'].value,"utf-8")
    band = unicode(form['band'].value,"utf-8")
    mode = unicode(form['mode'].value,"utf-8")
    if form.has_key('comment'):
        comment = unicode(form['comment'].value,"utf-8")
    else:
        comment = ''
    cur = con.cursor()
    try:
        cur.execute("INSERT INTO qsolog VALUES(datetime('now'),?,?,?,?,?,?)",
                    (callsign,rcvd,send,band,mode,comment))
        con.commit()
    except:
        con.rollback()
    finally:
        cur.close()

if form.has_key('search') and len(callsign) > 0:
    com = "SELECT * FROM qsolog where callsign = '{0}' ORDER BY utc DESC LIMIT {1}".format(callsign, conf.RESULT_LIMIT)
else:
    com = "SELECT * FROM qsolog ORDER BY utc DESC LIMIT {0}".format(conf.RESULT_LIMIT)

con.row_factory = sqlite3.Row
cur = con.cursor()
try:
    print( conf.TBL_START )
    for row in cur.execute(com):
        com = row['comment'].encode('utf-8')
        com = "<br>" if len(com) == 0 else com                # for null comment
        print( conf.RESULT.format(row['utc'].encode('utf-8'),
                                  row['callsign'].encode('utf-8'),
                                  row['rcvd'].encode('utf-8'),
                                  row['send'].encode('utf-8'),
                                  row['band'].encode('utf-8'),
                                  row['mode'].encode('utf-8'),
                                  com))
finally:
    cur.close()
    con.close()
print( conf.FINAL )

81行x120文字以内で、出来上がりました。横方向に120文字って、反則じゃんと言うのは 十分に承知してます。これもそれも、みんなSQLの英語もどきが悪いのよ。正直、SQLは 嫌いだったりします。

前回作った、コールサインのパターンを計算するルーチンを折角なので使ってみました。 入力されたコールサインが、あらかじめ登録しておいたパターンに合致しているか、 確認しています。なお、合致の確認には、集合演算を使ってみました。

検索は、like句を使おうかglob句を使おうか、ちょいと迷いましたが、そんなのは止めて 完全一致にしました。検索用のSQLは、SQLインジェクション防止の為、ブレースフォルダー を使うべきなんでしょうが、手抜きしちゃいました。ごめんなさい。

Python2.5から、3項演算が出来るというので、参考に使ってみました。

C言語で出てくる

var = test ? true_val : false_val

を、Python語に変換すると

var = true_val if test else false_val

のように、なります。これ、以外に便利だったりします。

conf.py

これ、上記log用の設定ファイル兼テンプレートです。

設定の所では、新たに、コールサイン用のパターン(集合)を、定義してます。本当なら、前回見た ように、100個ぐらいのパターンを登録しておかなければなりませんが、今回は手抜きしてます。

# -*- coding: utf-8 -*-

YOUR_CALL = 'JA8IOC'
RESULT_LIMIT = 12
PATTERN = set(['aaNaaa', 'aaNaa', 'aNaaa', 'aaNa', 'aNaa', 'NaNaaa', 'NaNaa', 'aNNaa'])

HEAD = '''Content-type: text/html; charset=utf-8"
Pragma: no-cache"
Cache-Control: no-cache"

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<title>{0}'s Log</title>
</head>
'''

BODY = '''<body>
<form method="POST" action="./log">
<table>
<tr>
  <td><b>CallSign</b></td>
  <td><input type="text" name="callsign" size="12" value="">
  <input type="submit" name="search" value="Search callsign"></td>
</tr>
<tr>
  <td><b>Rcvd RST</b></td>
  <td><input type="text" name="rcvd" size="4" value=""></td>
</tr>
<tr>
  <td><b>Send RST</b></td>
  <td><input type="text" name="send" size="4" value=""></td>
</tr>
<tr>
  <td><b>Band</b></td>
  <td><input type="text" name="band" size="6" value="7"></td>
</tr>
<tr>
  <td><b>Mode</b></td>
  <td><input type="text" name="mode" size="6" value="CW"></td>
</tr>
<tr>
  <td><b>comment</b></td>
  <td><input type="text" name="comment" size="60" value=""></td>
</tr>
<tr>
  <td colspan="2"><input type="submit" name="reg" value="Register">
  <input type="reset" value="clear"></td>
</tr>
</table>
</form>
'''

TBL_START = '''
<table  border=1 cellspacing=0 cellpadding=2>
<tr bgcolor="#cccccc" align=center><td>UTC</td><td>CallSign</td><td>Rcvd</td>
<td>Send</td><td>Band</td><td>Mode</td><td>Comment</td></tr>
'''

RESULT = '''
<tr><td>{0}</td><td>{1}</td><td align=right>{2}</td><td align=right>{3}</td>
<td align=right>{4}</td><td>{5}</td><td>{6}</td></tr>
'''

FINAL = '''
</tbl></body></html>
'''

テンプレート部分は、テーブルに検索結果を表示するようにしました。テーブルもいろいろと Bad knowHow が、あるようで、困ったものです。

画面のダンプ

これ、相変わらず、w3mのAAです。

CallSign [            ] [Search callsign]
Rcvd RST [    ]
Send RST [    ]
Band     [7     ]
Mode     [CW    ]
comment  [                                                            ]
[Register] [clear]

┌──────────┬────┬───┬───┬───┬───┬─────────────┐
│        UTC         │CallSign│Rcvd  │Send  │Band  │Mode  │        Comment           │
├──────────┼────┼───┼───┼───┼───┼─────────────┤
│2011-05-22 03:07:51 │KE5JA   │   354│   331│     7│AM    │Serial Number is 27997.   │
├──────────┼────┼───┼───┼───┼───┼─────────────┤
│2009-08-10 11:46:17 │KE5JA   │   586│   185│   3.5│RTTY  │Serial Number is 18986.   │
└──────────┴────┴───┴───┴───┴───┴─────────────┘

データベースのメンテナンス

以前、既製品のハムログを試してみたけど、データベース重要って事で、あちこちにバックアップを 取る事を推奨してた。

今回作った my/ham/log も同じことで、データベースはきちんとバックアップしときましょう。何、 難しい事ではありませんぜ。JA8IOC.db 相当のやつを、適当なタイミングで、USBメモリーにでも 放り込んでおけば十分。

Windowsで使っていて、それをLinuxへ持って行って使いたい場合、DBファイルだけ持って行けば、 それでOK。でも、エンディアンの違うCPUの場合どうなんだろう? バイナリーレベルの互換性あるのかな? 残念ながら、手元にはインテルの糞石マシンしかないので、検証出来ず。

たとえバイナリーレベルで互換性が無くても、一度DBをダンプしてから持って行ってそれを読み込ませれば用が足りるな。

[sakae@cdr ~/my]$ sqlite3 JA8IOC.db
sqlite> .output hogehoge
sqlite> .dump

hogehogeってファイルにDBの内容をダンプしてみた。

[sakae@cdr ~/my]$ cat hogehoge
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE qsolog(
utc      timestamp,
callsign TEXT,
rcvd     TEXT,
send     TEXT,
band     TEXT,
mode     TEXT,
comment  TEXT);
INSERT INTO "qsolog" VALUES('2011-05-15 03:25:11','8J1RL','599','589','7','CW','');
INSERT INTO "qsolog" VALUES('2011-05-15 03:30:17','JA0ZZZ','599','599','7','CW','試験ですよ');
   :
INSERT INTO "qsolog" VALUES('2008-05-13 05:39:41','JA0FVU','208','451','10','SSB','Serial Number is 43286.');
INSERT INTO "qsolog" VALUES('2011-05-23 12:13:54','8N1AA','44','55','21','CW','');
COMMIT;

見ての通り、冒頭でテーブルを作って、その後は永遠とデータをインサートするSQLコマンドが並んで いるテキストファイルだ。これならエンディアンの違うCPUでも大丈夫だね。

読み込む時は、

sqlite> select count(callsign) from qsolog;
86570
sqlite> .read hogehoge
Error: near line 3: table qsolog already exists
sqlite> select count(callsign) from qsolog;
173140

readコマンドを使えばいいんだけど、既にあるテーブルに追加って形になっちゃうんで、エラーは出るわ、 データは倍になるわと、散々な目に合わせられる。

[sakae@cdr ~/my]$ rm JA8IOC.db
remove JA8IOC.db? y
[sakae@cdr ~/my]$ sqlite3 JA8IOC.db
sqlite> .read hogehoge
sqlite> select count(callsign) from qsolog;
86570
sqlite> .q

手っ取り早く、DBファイルを削除し、それからやればOK。sqlite3コマンドで指定するDBファイルが 存在しなければ、自動作成されます。

また、場合によっては、CSVファイルにDBの内容を吐き出したいとか、逆に取り込みたい場合もあろう。

吐き出す場合

sqlite> .mode csv
sqlite> .output fuga.csv
sqlite> select * from qsolog limit 4;

吐き出されたやつは、EXCELでもどこでも持って行ってね。尚、outputでファイルを指定しちゃうと、以後の 出力はファイルに行っちゃうので、.output stdout で、画面に戻しておきましょう。

[sakae@cdr ~/my]$ cat fuga.csv
"2011-05-15 03:25:11",8J1RL,599,589,7,CW,""
"2011-05-15 03:30:17",JA0ZZZ,599,599,7,CW,"試験ですよ"
"2011-01-08 12:34:44",SP9RI,204,502,24,FM,"Serial Number is 0."
"2008-06-10 21:18:41",K9NO,123,324,14,FM,"Serial Number is 1."

逆にCSVファイルの内容を取り込む場合は

sqlite> .separator ","
sqlite> .import fuga.csv qsolog
sqlite> select count(callsign) from qsolog;
86574

セパレータを指定した後、インポートします。4データ増えている事が確認できます。

まとめ

もう、何回もやってるので、このシリーズはこれで打ち止めにしよう。最後に、まとめを書いて おきます。

世の中にはハム用のログプログラムは数あれど、Windowsでしか動かないとか、DOS用とか プラットフォーム限定ってのがほとんどでした。変なプラットフォームを使う人は、そこで諦めてJARLから紙ログを 買ってきてそれを使うのを余儀なくされてしまいます。

プラットフォームを越えて動く物を作ろう。そうすれば、便利だよね。世は正にWebの時代。 ならば、入出力装置として、Webブラウザーを使ってやれ。そうすればプラットフォームの 呪縛から逃れられるね。

そうすると、Web用のサーバーが必然的に必要となる。また、入力されたデータを記憶して おいて後で参照する必要もあろう。いわゆる、サーバー機能とデータベース機能も合わせて 必要となる。しかも、なるべく簡単にね!

そこで選んだのが、Python。バージョン2.5以上を選んで、インストールするだけで 上記の機能を満たしてくれる。

私が書いたのは、Webサーバーの接続条件等をちょっと変更するためのサーバープログラム (初回で解説)と ブラウザーとデータベースを結びつけるプログラム(と、その設定ファイル)だけ (今回解説)。 それを、所定のフォルダーに入れるだけで、即、運用が可能になる(初回で解説)。

Pythonで書かれているので、ちょっと勉強すれば、拡張も簡単。是非お試しあれ。