2010年 8月 28(土曜日)
YahooAuctionSの最近のヤフオク仕様変更に合わせた改版こちらの続きです。最近ヤフオクは公告収入の増収を狙って(?)、画面の大幅な変更と公告スペースの拡大を行いました。これに合わせてユーザスクリプトも変更しました。特に大幅に増えた公告対応をしました。こちらを見て、設定をカスタマイズしてご利用下さい。 スクリプトはこれ→yahooauctions.user.jsです。
2010年 8月 28(土曜日)
C#で二重起動を禁止するコード、起動済みプロセスに処理を移管するコードアプリケーションは通常、プロセスとして複数起動できるものですが、何かの理由で1つのみ起動できるようにしたい場合があります。例えば、唯一リソースにアクセスするものや、インスタンス間の連携のためにスレッドで実現したい場合などです。後者はWindowsでは良く見る仕様で、Officeがそうですし、Adobe Readerなどもそうです。また、Macでは複数のアプリを上げようとしても、既に起動しているインスタンスに切り替わります。 ここでは、単純に2つの仕様の実装を紹介します。 - 二重起動を単に阻止する。2つ目を上げようとしたときに警告を出して終了する。
- 2つ目を上げようとしたとき、既に起動しているプロセスが(あたかも起動したかのように)表示される。
二重起動を阻止プロセスの始めのほうの行に、以下のコードを入れます。Program.cs内で充分です。 // check not to run same processes simultaneously. if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1) { MessageBox.Show(Properties.Resources.isAlreadyRunning); return; }
要するに、起動プロセスと同じ名前のプロセスがあったら、警告ダイアログを出して終了します。 起動済みプロセスに処理を任せる前述のMacのような仕様です。起動しようとするユーザは、そのタスクを実行したいのですから、「できない」とか言われても困ってしまいます。できる限り、ユーザがやりたいことを実現するべきでしょう。2つ目を上げようとすると、起動できたように見えて、実は1つ目のプロセスのウィンドウがアクティブ状態で表示されます。 なのですが、こちらのほうは実装がすっきりしません。本来であれば、起動済みプロセスにお知らせして、実行を代行してもらうように実装すべき(UNIXであれば、このように実装するでしょう)なのですが、手軽ではありません。そこで、WinAPIを使って、表示を制御してしまいます。 // check not to run same processes simultaneously. if (ActivateRunningProcess()) { return; } ---------- // Activate the process, if a instance is already running. private static bool ActivateRunningProcess() { string pnm = Process.GetCurrentProcess().ProcessName.ToString(); string cls = "WindowsForms10.Window.8.app.0.33c0d9d"; IntPtr hWnd = FindWindow(cls, pnm); if (hWnd == IntPtr.Zero) { //-- With debugger .vshost must be added after the processname. string pnmr = pnm.Replace(".vshost", ""); if (!pnmr.Equals(pnm)) { hWnd = FindWindow(cls, pnmr); } } if (hWnd == IntPtr.Zero) { return false; } bool rts = ShowWindowAsync(hWnd, SW_RESTORE); bool rtf = SetForegroundWindow(hWnd); if (!rts || !rtf) { MessageBox.Show(Properties.Resources.isAlreadyRunning + "(" + (rts?"1":"0") + "," + (rtf?"1":"0") + ")"); } return true; } [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); private const int SW_RESTORE = 9; // Restore the window size
起動済みのアプリプロセスを捜すのに、FindWindowを使っています。本当は、GetProcessByNameで捜すべきところなのですが、これでやると、プロセスは見つかりますが、MainWindowHandleが取れない場合があります。デバッグした範囲では、殆どの場合でOKなのですが、ステータス表示のみ(アイコンボックスに表示されているだけ)の常駐状態の場合には無効なWindowHandleが戻ってきます。仕様(実際にMainWindowが作成されていない/破棄されているのかも)なのか、バグなのか分りません。 FindWindowでは、アプリ名のフォルダーを開いているExplorerのプロセス(というかウィンドウ)も掴まえてしまうため、実際上、クラスの指定が必須になります。これは Spy+ で確認できますが、呪文を含む文字列で中身は良く分りません。とりあえず、ビルドし直しても、VisualStudioのバージョンを変えても大丈夫なようです。 また、Visual Studio 2010から、デバッグ時のプロセス名に ".vshost" が付くようになっているようです。この部分のコードは、デバッグ時以外は必要ありません。 まとめこれで、2重起動への対応ができました。上記以外で、通常必要になるかもしれない機能は、起動パラメタを渡すことくらいでしょうか。 WinAPIは既に使うべきではないようですが、便利なので使い続けられて行くでしょう。なんと言うか反則じみていますし、結構危険に思われます。例えば、今回のコードでも、他のプロセスから勝手に表示制御などされてしまえば、最悪プロセスが落ちても文句言えませんよね。稼働中のプロセスの任意のタイミングでスクリーンにマップされてしまう訳で、表示(show)のコールバックなどもアプリが予期せぬタイミングで呼ばれてしまいます。普通に実装していれば動くでしょうが、何らかの状態制御や排他制御などをしていれば必ずしも安全ではありません。
2010年 7月 24(土曜日)
YahooAuctionSの改版こちらの続きです。 先週ヤフオクの検索画面が変更されましたが、昨日更にマイナーチェンジが施されています。検索時のブラックリストが効かなくなりましたので、改修しました。こちらyahooauctions.user.jsです。
2010年 7月 18(日曜日)
ヤフオク ユーザスクリプト: 新検索画面対応こちらのバージョンアップです。 ヤフオクの検索画面がリニューアルされましたので、これに対応しました。 - 今迄の機能を新検索画面のフォーマットに合わせました。
- 各商品のページを閲覧しても未読に戻ってしまう使い難い仕様を旧来のものに戻しました。
こちら yahooauctions.user.jsをクリックするとインストールされます。設定が初期化されてしまいますので、再設定する必要があります。 設定のページを参照いただき、元々のスクリプトファイルをyahooauctions.user.jsをバックアップ(例えばデスクトップにコピー)します。次に上記リンクからユーザスクリプトをインストールします。インストールされたユーザスクリプトファイルに、バックアップしたファイルから、設定をコピペ編集します。 # そろそろ設定をスクリプトファイルから切離さないと使い難いですね。
2010年 7月 5(月曜日)
Firefox以外のGreaseMonkeyユーザスクリプト実行環境Firefoxには、ウェブページのGUIをカスタマイズするユーザスプリプト実行環境Greasemonkeyがあります。私もユーザスクリプトを2つ公開しています。最近はWindows環境のIEやChromeを使うことがありますので、そこでもユーザスクリプトを使うことができないか、と思い、調査してみました。 IEとChromeに関してはGreasemonkey互換環境があり、YahooAuctionSは問題なく動作しました。 インターネットエクスプローラIEには2種類あるようです。TrixieとGreasemonkey for IEです。ネイティブな記述言語は、前者はJavascript、後者はDHTMLです。Trixieのほうが歴史が古く、Greasemonkeyとの互換性という点では良さそうです。 TrixieTrixieは外部プログラムになっており、インストールもMSIファイルになります。インストールすると、「ツール」メニューの中に"Trixie Options..."というメニューが追加され、設定を行うことができるようになります。ユーザスクリプトは、<<C:\Program Files\Bhelpuri\Trixie\Scripts\>>にコピーすれば自動的に読み込まれます。(読み込まれない場合、Trixie OptionsでReload Scriptsボタンを押します)。 YahooAucitonSしか動かしていませんが、特に動作に問題はないようです。 Google Chrome今年の春に組込み機能としてサポートされたようです。基本的に環境のインストールは必要がなく、ユーザスクリプトをファイルとしてブラウザに読み込ませることでインストールされ、それ以降はスクリプトが動作するようになります。こちらもYahooAuctionSの動作には問題はありません。 しかし、一つ問題があります。それは、ユーザスクリプトがインストールされるフォルダのパスが可読な形式でないことです。例えば、私のところでは、こんなパスです→<<C:\Users\XXXX\AppData\Local\Google\Chrome\User Data\Default\Extensions\ecegpedmokfjpiaofpacmkejhjfidfbf\1.0\>>。つまり、Extensionsの下に直に(他のAddOnと同列に)置かれてしまい、かつ、GUIDのような無意味な文字列のフォルダ名にされてしまいます。しかも、その下は、"manifest.json"と"script.js"とファイルが自動生成されています。(後者はyafooauctions.user.jsをリネームしたもの)。このため、どこにインストールされたかは、ファイルを開けて捜さないと分りません。 現在、YahooAuctionSは設定UIを持たず、設定変更はスクリプトファイルの直接編集になっていますので、ファイルの場所が分らないというのは問題です。設定UIを開発しないと一般ユーザには使っていただけない状況かと思います。 おまけで更新しました。マイオークションメニューの「値下げ交渉」リンクを「検索履歴」に差し替えることができるようになります。「値下げ交渉」というのは使われているのでしょうか? 検索履歴は良く使うので代りにメニューに入れました。 var SUBSTITUTE_OFFERDISCOUNT_TO_SEARCHHISTORY = true;
とすることでメニューが切り替わります。 yahooauctions.user.js ※ (7/7追記 前回「終了したウォッチリストの削除から戻る」が間違ったページに戻るようになっていたのを修正)
2010年 7月 5(月曜日)
blogシステムserendipityをchromeで編集できるようにするパッチChromeブラウザでserendiputyの管理者スィートを立ち上げた場合、警告(alert)が度々表示される不具合があります。"mozilla < 1.3 beta is not supported"というものです。これをfixするパッチを作りました。 serendipity-chrome.patch これは、Safari系ブラウザに共通なもののようです。このアラートは、Geckoエンジン(SafariはKHTMLのような?)の場合に、リリース日時を調べて、一定以上昔の場合に表示されています。通常はこれでOKなのですが、Safariの場合はちょっと動作が違っていて、初期リリース(1.0版)のリリース日時を返すとのこと。このため、必要以上に古い日時が返されて警告が表示されていました。 Safari系のブラウザ(Safari, Chrome)は新しく、時期的に"< mozilla 1.3"とかいうことはありませんので、単純に警告を抑制することにしました。実際には、AppleWebKitを使っているものに対して抑制します。調べていませんが、このモジュールが上記動作をしていると思われます。 もしかしたら、serendipityの最新リリースでは直っているかもしれません。(ソースコメントに、この部分の実装が不完全なことが注記されていますので)
2010年 6月 23(水曜日)
YahooAuctionSの機能追加ヤフオク向けGreaseMonkeyユーザスクリプトの機能を追加しました。こちらyahooauctions.user.jsです。 変更点は以下の通りです。 - ウォッチリスト画面の表示を「タイトルと画像」に設定するスイッチ(FORCE_TITLE_AND_PICTURE_ON_WATCHLISTをtrueにして下さい)の追加。
- 上記を設定するとウォッチリストが20個/画面になってしまうのを、50個/画面とする。
- ウォッチリストの削除画面から自動で戻る設定をしたときに、"終了したオークション"の削除画面から"開催中のオークション"に戻ってしまう不具合の修正。
なお、"設定する"と言っても、ユーザがボタンを押して、表示を明示的に変更した場合は、そちらが優先されます。 インストールFirefoxで、GreaseMonkeyのアドオンがインストールされていれば、単にクリックしていただくだけでインストールされます。詳しい設定方法は、こちら"ヤフオク用Greasemonkeyスクリプト II"をご覧下さい。 ※ FORCE_TITLE_AND_PICTURE_ON_WATCHLIST に不具合があったので、差し替えました。不具合は、設定しても最初の一回目は反映されないというものです。
2010年 6月 4(金曜日)
ヤフオク ユーザスクリプトの改良ヤフオク画面用ユーザスクリプトを改造しました。改造点は2点です。 - ソート方法が既に指定されていたら、そのまま表示します。
例えば、「価格が安い順」とユーザが設定したらそれに従い、残り時間で再ソートはしません。 - リスト表示形式が、「画像」(画像のみのマトリックス表示)のときにもブラックリストが効くようにしました。
ファイルはこちらyahooauctions.user.jsです。
2010年 5月 28(金曜日)
SKKの入れ替え嵌り易い漢字変換システムにSKKがあります。その名の通りシンプルな単語変換ですが、入力の手順が手書きと似ているところが気に入っています。元々はEmacsに特化したシステムだったこともあり、ユーザも多くはないながらも熱心な人が多いようで、廃れることもなかったので、利用し続けてきました。また、幸いなことに今ではLinux(iBus, SCIM, UIM, XIM), Windows、Macなど多くの環境で利用することができます。 やっと、受託していた開発を納品することができましたので、なんだか無駄なことがしたくなり、SKK関係の不満を解消しようと思い立ちました。 SKKIME on WindowsWindowsにはSKKIMEがあります。本当に素晴しい。これと、XKeymacsがあるおかげでWindows上でも快適に作業をすることができるようになりました。しかし、私が使っていたバージョンでは「IMEをオープンしたときに"かな"に戻す」の設定が効いていないことが、小さな不満でした。特にWindowsではステータスバーがウィンドウから離れた端っこのほうに出るので、いちいち確認していられないのです。 さて、サイトを見てみると新しい版が出ていますので、入れ替えてみました。設定が大幅に変わっているようで、Shift+Spaceを押しても動きません。どうやら、この辺のキーハンドルが自分でできるように変更されたようです。このため、XKeymacsで設定していた"toggle-ime"のキーマップを解除し、skkimeのプロパティ設定のキーマップの設定で、Shift-Spaceを追加して、toggle-imeを設定。これでできました。最新版では、上記の「"かな"に戻す」も動作しています。 この設定を見ていると、もっとカスタマイズできそうなので、以前から気になっていたのを変更することにします。 skkでは"\"(あるいは¥)で句点コード入力モードに入りますが、UNIXと違い、Windowsではディレクトリのセパレータとして"\"自体を入力することが多く、この仕様はちょっと困りものでした。要するに、"\"で"\"が入力されるようにします。同じくキーマップ設定で"\"の "skk-j-mode-map"を"self-insert-char"にして、別のキーに"skk-input-by-code-or-menu"を割当てます。IME起動と"Shift+Space"にしているために空いている"C-\"に設定しました。 SkkInput3 on Linuxこれで気を良くしたので、Linuxのほうも変更してみることにしました。ディストリビューションにはskkinput2が入っているため、こちらをバージョンアップしつつ使っていたのですが、何かの拍子に落ちてしまうこと(しばしばFirefoxと共に落ちる)や、漢字登録ダイアログが消去できなくなってしまうことがあり、解決策を捜していました。 バージョン3版を取ってきてコンパイルしてみると動きそうです。(以前は何故か動かなかった記憶が)。とりあえず、実行ファイルも同じskkinputになるので、同じように使うことができますが、共存は想定されていないようです。 こちらも、そのままでは動かないようです。Xリソースに書いてある設定は効いていないようで、emacs-lispの設定ファイルを作らなくてはなりません。で、".skkinput.el"を作ります。とりあえず、".skk"にリンクすれば良さそうです。(ln -s .skk .skkinput.el) こちらもしばらく使っていた限りでは、前述の問題は解決されています。落ちないようですし、登録ダイアログの動作も申し分ありません。なのですが、原因不明の不具合が発生しており、一時的にバージョン2に戻していて、そのまま乗り換えには至っていません。不具合はFirefox上で"Enter"が入力できなくなってしまうことがある、のと、Backspaceが誤動作(?)することです。skkinput以外の何かの設定が原因かもしれません。
2010年 4月 11(日曜日)
VIA VT6102イーサネットコントローラの不具合解決VIA Technologies Inc.社のチップセットKT600(多分他のファミリにも)には、VT6102(6105とかも)イーサネットコントローラ(NIC)が付いています。このNICはRhine/Rhine IIと呼ばれていて、勿論Linuxでもサポートされているのですが動作上の不具合があり、今に至るもfixされていないようです。しかし、VIAが提供するドライバを使えば解決することが解りました。 不具合の内容不具合は、このデバイスをLinuxで使うとき、長時間コネクション繋ぎっぱなし、もしくは連続して大量のデータの通信をすると、"NETDEV WATCHDOG: eth0: transmit timed out"というwatchdogタイマによるリセットが入ってコネクションが切断されてしまう、というものです。リセット後は再度接続可能になるため、Webなど小まめに切断される通信では問題となりませんので、殆どのユーザは気付かずに使っているかもしれません。 しかし、サーバとして使うなどして、大きなファイル転送などを行うと途中で切れてしまうので、使い物になりません。また、Windowsではこのような不具合は報告されていないようですので、原因はハードウェアバグとしてもドライバソフトウェアでの回避は可能なはずです。 以前に一回調査してみたのですが原因が掴めていないようで、カーネルの残バグとして残っています。対症療法として、ACPIを切れば動くとか、APICを使わなければ動くとか書いてあるものもあり、やってみましたが直りませんでした。(Linuxカーネルでは、何か不具合あると、仕様が必ずしもオープンでないACPIやAPICに原因を求めようとする悪い癖があるような。度々grub.confに色々書き込んで試しています)。結局仕方なく、別途NICカードを差して対処していました。 VIAのドライバ今回、ひょんなことからVIAがLinux向けにドライバの提供を行っていると知り、もしやと思って捜してみたらRhine II向けもありました。ソース提供なのでコンパイルして"rhinefet"というモジュールをロードしてみます。今迄使ってみていますが、不具合は出ていないようです。作業で1GBx40個のファイルを転送しましたが大丈夫です。有難いことです。もしも、この不具合に悩まされているサーバ屋さんがいらっしゃいましたら、このドライバを試してみることをお勧めします。 それにしても、Linuxのサポートが全くなかったり、不具合以外のfirmwareやBIOSのupdateを行わないのは、今や日本メーカだけなのではないでしょうか。酷い場合には、アメリカサイトでは提供しているのに、日本サイトだけには置いていなかったりします。最近もF通のPC(Inetl 865チップセット)でPrescottが使えなくて残念な思いをしました。以前はサポートのことも考えて国内メーカ製の機器を選んでいましたが、今は韓国や台湾を始めとする海外メーカのものを選んでしまいます。 提供してもし不具合が出たらとか、コールセンタへの問い合わせが増えたら困るということなのでしょうけれど。そろそろこういった社内対応優先主義、あるいは事なかれ主義のようなものはやめていただきたいですね。「お客様第一」などと口では言いますが、全然そうなっていません。
2010年 3月 31(水曜日)
watch_videos 形式のURL対応YouTubeにいつの間にか仕様が追加されています。URLに"watch_videos"が付くものがそれで、例えば、<<http://www.youtube.com/watch_videos?more_url=%2F&video_ids=BOjWxqFKA4g....>>の形式です。 YouTubeダウンローダとダウンロードスクリプトでこの対応をしました。このフォーマットのURLのページを開いてみた範囲では、"watch?v=xxx"のURLのページと見た目に違いはありません。このため動作上の違いは分りませんでしたが、いずれにせよ、動画が自動再生されますので、このビデオを取得することになります。 ところで、ソフトウェア的にもこのフォーマットには謎があります。このURLにアクセスすると、<<http://www.youtube.com/watch?v=BOjWxqFKA4g&...>>に転送(リダイレクト)されます。転送先は一見何の変哲もないいつものフォーマットのページのようなのですが、ここから取得した"fmt_url_map"を使うと、直接ダウンロードできないようなのです。取得できるビデオ識別子(watch?v=xxx)を使えばダウンロードできるのですが、実際、見比べてみますと、この2つの"fmt_url_map"は確かに違っているのですが、どう直すのが正解なのか今一不明です。 そこで、ビデオ識別子を使ってページを取得し、その中から"fmt_url_map"を取ることにしました。ビデオ識別子は、"video_ids"パラメタの最初のもののようです。しかし、このフォーマットのURLはサンプルが少ないため確証が持てなかったので、取得できなかった場合はリダイレクトページの中からビデオ識別子を捜すようにしました。 こんな感じです。 #--- YTPid from watch_videos type URL sub GetYtpidFromWatchVideos { my ($url) = @; my $ytid = ""; my $durl = urldecode($url); #--- like http://www.youtube.com/watch_videos?more_url=/ #--- &video_ids=BOjWxqFKA4g,yHeiXcg1M5c,Ki4dgped5d0,2D296Xi2FLY #--- &type=11&feature=featured&no_autoplay=1 if ($durl =~ m![?&]video_ids=([^&,;]+)!) { $ytid = "http://www.youtube.com/watch?v=${1}"; } else { $ytid = GetYtpidFromWatchVideosFromWeb($url); }
Dprint "GetYtpidFromWatchVideos($url)->($ytid)"; return $ytid; } sub GetYtpidFromWatchVideosFromWeb { my ($url) = @_; my $ytid = ""; my $opts = "-q -U \'$UAgent\'"; if ($url =~ m!(http://[^/]+/)!) { $opts .= " --referer=\"${1}\""; } my $cmd = "wget $Options -O - \'$url\'"; Dprint "COMMAND: $cmd"; my @pgcontents = `$cmd`; foreach (@pgcontents) { # Dprint "pgcontents: $_"; chomp(); if (m!<link rel=\"canonical\" href=\"([^\"]+)\">!) { $ytid = "http://www.youtube.com$1"; } elsif (m|\'VIDEO_ID\': \'([^\']+)\',|) { $ytid = "http://www.youtube.com/watch?v=${1}"; } } Dprint "GetYtpidFromWatchVideosFromWeb($url)->($ytid)"; return $ytid; }
これにて、取得に失敗していたビデオが所得できるようになったと思います。
2010年 3月 17(水曜日)
Linuxで使える世界時計
サーバのメンテナンスのために休止したり、データを入れ換えるのは、利用者になるべく不便を掛けないような時刻を選ぶようにしています。最近は海外からのアクセスが結構あるので、海外の時刻が何時なのか気になっていました。ヨーロッパとの時差は何となく実感としてあるのですが、アメリカは今何時なのだろう、と。 それで、良く証券会社の仕事場の映像に出ているような、壁に世界中の都市の時刻を表示する時計が一杯並んでいるようなものが欲しいなと思いました。
検索してみると、意外なことに、こういった世界時計は多くありません。なんだか平気で有料シェアウェアとかになっています。時差があるだけで簡単なものだと思うのですが。このため、時計を横に並べたようなものを自分で作ってみようかとemiclockのソースを取ってきたりしたのですが、残念ながらこれはGPLではないため改造して使うことはできないようです。 zdumpそうこうしている内に、zdumpというプログラムを見つけました。使い方は簡単で、 $ zdump Europe/Pais Europe/Paris Wed Mar 17 06:54:09 2010 CET
これで現在時刻の表示ができました。色々な場所を一度に表示したければ、 $ zdump Europe/London Australia/Sydney Asia/Tokyo Asia/Singapore Asia/Katmandu Europe/Paris \ Europe/Moscow America/New_York America/Los_Angeles Africa/Kinshasa Pacific/Honolulu Europe/London Wed Mar 17 05:55:57 2010 GMT Australia/Sydney Wed Mar 17 16:55:57 2010 EST Asia/Tokyo Wed Mar 17 14:55:57 2010 JST Asia/Singapore Wed Mar 17 13:55:57 2010 SGT Asia/Katmandu Wed Mar 17 11:40:57 2010 NPT Europe/Paris Wed Mar 17 06:55:57 2010 CET Europe/Moscow Wed Mar 17 08:55:57 2010 MSK America/New_York Wed Mar 17 01:55:57 2010 EDT America/Los_Angeles Tue Mar 16 22:55:57 2010 PDT Africa/Kinshasa Wed Mar 17 06:55:57 2010 WAT Pacific/Honolulu Tue Mar 16 19:55:57 2010 HST
というふうにできます。いつも一時に表示したければ、こんな感じで良いでしょう。 $ alias wclock='zdump Europe/London Australia/Sydney Asia/Tokyo Asia/Singapore Asia/Katmandu \ Europe/Paris Europe/Moscow America/New_York America/Los_Angeles \ Africa/Kinshasa Pacific/Honolulu'
worldclock.plさて、これで充分なのですが、ディストリビューションやバージョンによってはzdumpが動かないことがあるようです。何かのバージョン不整合があったのか、私のデスクトップマシンでは最初動きませんでした。いつも同じ時刻(グリニッジ標準時か東京時刻)を表示してしまいます。これは結局zoneinfoのインストールが上手くなかったようで、zoneinfoの少し新しいバージョンを入れたら動作するようになりました。元々はzoneinfo(/usr/share/zoneinfo)に.ics(中身はVCalendar)のファイルしかなかったのですが、追加したら拡張子が付かないバイナリファイル(TZif形式)が追加されてました。 それで、動かない間に、元々の.icsファイを読んでzdumpと同じ動作をするworldclock.pl(worldclock.pl.zip)を作成しました。こちらは少しおまけが付いていて、都市だけで、もしくは都市名に完全に一致しなくても、マッチするものを表示します。 $ worldclock.pl Par 03/17 Wed 03:19:47 2010 SRT America/Paramaribo 03/17 Wed 07:19:47 2010 CET Europe/Paris
こちらも、都市名(Aria/City)を複数指定して、上記のwclockのようにalias等を設定することができます。 #!/usr/bin/perl $Usage = "$0 [-h] [-verbose] [Aria/]city ...\n";
$ZoneinfoDir = "/usr/share/zoneinfo"; @WeekdayName = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); @MonthName = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); $Verbose = 0;
#--- interpret the command-line arguments while ($ARGV[0]) { # print STDERR "$ARGV[0]\n"; if ($ARGV[0] =~ /^-D.*/) { $Debug = 1; } elsif ($ARGV[0] =~ /^-v.*/) { $Verbose = 1; } elsif ($ARGV[0] =~ /^-.*/) { print STDERR "$Usage"; exit 0; } else { last; } shift(@ARGV); }
#--- checking to set the necessary variables. #--- DebugPrint function #$Debug = 1; sub Dprint { if (defined($Debug)) { printf STDERR "DBG @_"; } return 0; } Dprint "ARGS($#ARGV): @ARGV\n";
#--- Current time of Greenwich. $gm_time = time() - 9 * 60 * 60;
#--- Check zoneinfo dir if (!-d $ZoneinfoDir) { my @zdirs = `locate /zoneinfo/Asia/Tokyo.ics`; #-- Use the first one without reasons. if ($#zdirs < 0 || $zdirs[0] !~ m|^(.*)/[^/]+/[^/]+$/|) { printf STDERR "$0 zoneinfo directory is not found."; exit(-1); } $ZoneinfoDir = $1; Dprint "ZoneinfoDir ($ZoneinfoDir)\n"; }
#--- Create file list foreach my $arg (@ARGV) { my @matchs_complete = (); my @matchs_partial = (); my $sout = ""; if ($arg =~ m|/|) { #-- Asia/Katmandu if (-f "$ZoneinfoDir/$arg.ics") { push(@matchs_complete, "$ZoneinfoDir/$arg.ics"); } @matchs_partial = glob("$ZoneinfoDir/${arg}?*.ics"); } else { #-- Katmandu @matchs_complete = glob("$ZoneinfoDir/*/$arg.ics"); @matchs_partial = glob("$ZoneinfoDir/*/${arg}?*.ics"); } Dprint "Complete: (@matchs_complete)\n"; Dprint "Partial : (@matchs_partial)\n"; foreach my $zfile (@matchs_complete) { $sout = GetLocalTimeString($zfile); print "$sout\n"; } foreach my $zfile (@matchs_partial) { $sout = GetLocalTimeString($zfile); print "$sout\n"; } }
exit 0;
#--- sub GetLocalTimeString { my ($zf) = @_; my $srtn; open(IN, "<$zf") or return ""; my @czf = <IN>; close(IN); my ($tzsign, $tzhour, $tzmin, $tzname) = ("", "", ""); foreach (@czf) { if (m|TZOFFSETTO:([+-])([0-9]{2})([0-9]{2})|i) { ($tzsign, $tzhour, $tzmin) = ($1, $2, $3); } elsif (m|X-LIC-LOCATION:(.+)|i) { $tzlocation = $1; } elsif (m|TZNAME:([a-zA-Z]{3})|i) { $tzname = $1; } } Dprint "($tzsign, $tzhour, $tzmin, $tzname)\n"; if (length($tzhour) <= 0 || length($tzmin) <= 0 || length($tzsign) <= 0) { return ""; } my $lc_time; if ($tzsign eq "-") { $lc_time = $gm_time - ($tzhour * 60 + $tzmin) * 60; } else { $lc_time = $gm_time + ($tzhour * 60 + $tzmin) * 60; } Dprint "($gm_time, $lc_time)\n"; my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($lc_time); if ($isdst) { #-- The aria supports summer time. ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($lc_time + 60 * 60); $tzlocation .= " (ST)"; } $srtn = Fmt($mon + 1, 2) . "/" . Fmt($mday, 2) . " $WeekdayName[$wday] " . Fmt($hour, 2) . ":" . Fmt($min, 2) . ":" . Fmt($sec, 2) . " " . ($year + 1900) . " $tzname " . "$tzlocation";
return $srtn; }
#--- sub Fmt { my ($nbr, $kta) = @_; return sprintf("%0${kta}d", $nbr); }
因みにzoneinfoがあるディレクトリは/usr/share/zoneinfoだと思いますが、これがなかった場合、あるいはあってもVCalendar形式ファイルがなくTZifのバイナリファイルだけの場合には、システム中からlocateで捜します。evolutionの下にあったりするようです。
2010年 3月 16(火曜日)
Type-Map (var)の作成前回、ウェブページの国際化に関して書きました。結論としては、type-mapを使って、.varファイルを作成すれば良いということでした。このtype-mapファイル作成を手作業でやるのは面倒ですので、生成スクリプトmktypemap.pl.zipを作りました。 使い方は以下の通りです。 カレントディレクトリのhtmlファイルとjpgファイルに対してlvarファイルを作成します。 mktypemap.pl *.html カレントディレクトリのファイル全部を対象とし、既にvarファイルがあるときは上書きします。 mktypemap.pl -f .
ここでは、"foo.php"に対して国際化されたファイルが"foo.ja.php"もしくは"foo.php.ja"の形式に対応します。 中身は以下のようなものです。 基本的に各々のファイルに対して言語違いファイルを捜して、それらをまとめて".var"ファイルを作成します。 #!/usr/bin/perl $Usage = "$0 [-h] [-S[imulate]] [-force] [-quiet] [--] files_to_create_var\n";
$Simulate_Mode = 0; #-- all procedure is simulated without creating files. $Verbose = 1; #-- print out many messages. $ForceOverwrite = 0; #-- When not set, .var file isn't created if exist.
#--- interpret the command-line arguments while (defined($ARGV[0])) { # print STDERR "$ARGV[0]\n"; if ($ARGV[0] =~ /^-D.*/) { $Debug = 1; } elsif ($ARGV[0] =~ /^-q.*/) { $Verbose = 0; } elsif ($ARGV[0] =~ /^-f.*/) { $ForceOverwrite = 1; } elsif ($ARGV[0] =~ /^-S.*/) { $Simulate_Mode = 1; } elsif ($ARGV[0] =~ /^-.*/) { print STDERR "$Usage"; exit 0; } elsif ($ARGV[0] =~ /^--/) { shift(@ARGV); last; } else { last; } shift(@ARGV); }
#--- checking to set the necessary variables. #--- DebugPrint function #$Debug = 1; sub Dprint { if (defined($Debug)) { printf STDERR "DBG @_"; } return 0; } Dprint "ARGS($#ARGV): @ARGV\n";
#--- If there are no files in arguments, all files in the current directory should be checked. if ($#ARGV < 0 || $ARGV[0] eq ".") { @files = glob("*"); } else { @files = @ARGV; }
#--- Create file list %typemap_created = (); #--- type-map file created by the program. foreach my $fle (@files) { my ($bdy, $lng, $ext); if (!-f $fle || -d $fle || $fle =~ m|\.var$|i) { next; } elsif ($fle =~ m|~$|i) { next; } elsif ($fle =~ /\.(html|htm)$/i) { #--- at this moment, html is a dummy to transfer to php. next; } elsif ($fle =~ m|^(.*)\.([^.]{2})(\.[^.]+)$|i) { #--- like foo.de.html ($bdy, $lng, $ext) = ($1, $2, $3); Dprint "(FILE 1: ($fle)($bdy, $lng, $ext)\n"; } elsif ($fle =~ m|^(.*)(\.[^.]+)\.([^.]{2})$|i) { #--- like foo.html.de ($bdy, $lng, $ext) = ($1, $3, $2); Dprint "(FILE 2: ($fle)($bdy, $lng, $ext)\n"; } elsif ($fle =~ m|^(.*)\.([^.]{2})$|i) { #--- like foo.de ($bdy, $lng, $ext) = ($1, $2, ""); Dprint "(FILE 3: ($fle)($bdy, $lng, $ext)\n"; } elsif ($fle =~ m|^(.*)(\.[^.]+)$|i) { #--- like foo.html ($bdy, $lng, $ext) = ($1, "ja", $2); Dprint "(FILE 4: ($fle)($bdy, $lng, $ext)\n"; } else { next; }
if ($lng eq "js" || $ext eq ".js") { #-- should be a java script file. next; } if (!$ForceOverwrite && -f "$bdy.var") { next; } elsif (exists($typemap_created{"$bdy.var"})) { next; } my $str = CreateTypeMap($bdy, $ext); if (length($str) > 0) { my $fvar = "${bdy}.var"; if (!$Simulate_Mode) { open(FOUT, ">$fvar") or next; printf FOUT "$str"; close(FOUT); } $typemap_created{$fvar} = 1; } }
exit 0;
#--- Creating contents of type map file sub CreateTypeMap { my ($bdy, $ext) = @_; my @grpfiles = glob("${bdy}${ext} ${bdy}.*${ext} ${bdy}${ext}.??"); Dprint "CreateTypeMap: ($bdy, $ext)\n"; Dprint "@grpfiles\n"; my $s = ""; foreach (@grpfiles) { my ($b, $l, $e); Dprint "CreateTypeMap: ($_)\n"; if (!-f $ || -d $ || /\.var$/i) { next; } elsif (/~$/i) { next; } elsif (/\.(html|htm)$/i) { #--- at this moment, html is a dummy to transfer to php. next; } elsif (/^(.*)\.([^.]{2})(\.[^.]+)$/) { #--- like foo.de.php ($b, $l, $e) = ($1, $2, $3); } elsif (/^(.*)(\.[^.]+)\.([^.]{2})$/) { #--- like foo.php.de ($b, $l, $e) = ($1, $3, $2); } elsif (/^(.*)\.([^.]{2})$/i) { #--- like foo.de ($b, $l, $e) = ($1, $2, ""); } elsif (/^(.*)(\.[^.]+)$/) { #--- like foo.php ($b, $l, $e) = ($1, "ja", $2); } else { next; } if ($l eq "js" || $e eq ".js") { #-- should be a java script file. next; } Dprint "CreateTypeMap: ($b, $l, $e)\n"; my $hder = "URI: $_\nContent-Language: $l"; $cty = GetContenttype($e); if ($cty eq "text/html") { $chs = GetCharset($l); $ctyp = "Content-type: text/html; charset=$chs\n"; } else { $ctyp = "Content-type: $cty\n"; } if (length($s) > 0) { $s .= "\n"; } $s .= "$hder\n$ctyp"; } #if (length($s) > 0) { # $s = "URI: $bdy; vary=\"type,language\"\n\n" . $s; #} Dprint "$s\n"; return $s; }
#--- Get the charset sub GetCharset { my ($lg) = @_; my $c; if ($lg eq "ja") { $c = "UTF-8"; } else { $c = "ISO-8859-1"; } return $c; } #--- Get the content type sub GetContenttype { my ($e) = @_; my $c; if ($e =~ /(jpg|jpeg)$/i) { $c = "image/jpeg"; } elsif ($e =~ /gif$/i) { $c = "image/gif"; } elsif ($e =~ /png$/i) { $c = "image/png"; } elsif ($e =~ /(php|html|htm)$/i) { $c = "text/html"; } elsif ($e =~ /zip$/i) { $c = "application/zip"; } elsif ($e =~ /(tgz|tar)$/i) { $c = "application/x-tar"; } else { $c = "application/octet-stream"; } return $c; }
なお、このコードでは、"#--- at this moment, html is a dummy to transfer to
php."のところにあるように、".html"に対しては".var"ファイルを作成しません。これは私は".php"としてコンテンツを作成している
ためで、".html"は昔からあるコンテンツや外部からのリンクが切れないように転送ページを置いています。 このファイルは以下のようなものです。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html lang="ja"> <head> <title>301 Moved Permanently</title> <meta http-equiv="refresh" CONTENT="0;URL=index.var"> <meta name="robots" content="NOINDEX,NOFOLLOW"> </head> <body> このページは、<a href="index.var">http://www.nikep.net/</a>に移転しました。 自動転送されない場合は、このアイコン<a href="index.var"><img src="home3.png"></a>を 押して下さい。<br/> Requested page was transfered to <a href="index.var">http://www.nikep.net/</a>. If you can see this text, please click the following icon <a href="index.var"> <img src="home3.png" alt="nikep"></a>. </body> </html>
2010年 3月 11(木曜日)
Apacheの多言語化壁紙サービス(アプリ)を国際化して、海外のサイトで配布しています。意外にも、国内よりも海外のほうで使ってくださる人が多く、サイト自体のアクセスも海外からが増えてきました。そのため、最近は少しずつ英訳を進めています。しかし、ページが増えてくると、色々と課題が出てきて手作業が増えてきましたので、システマチックな方法で対応することにしました。 ページの英訳ページを英訳するには、以下の作業を行います。 - 内部テキストを英語に翻訳します。
- ページに記載されたハイパーリンクで、既に翻訳済みページがある場合は、そちらを差すようにします。埋め込みのイメージファイルに関しても同様にします。
- 当該ページにアクセスする際の(別ページ等にある)リンクを、英語言語リーダ向けには英語のページに変更します。
この内、テキストの翻訳は手で行うしかありませんが、2)や3)は手作業でやるのが手間になってきたのと同時にバグったりしてきました。このため、2)に関してはPHPで関数を書いて対応していたのですが、3)の対応を行うに際し、Web Server (Apache)の機能を調べてみました。 国際化の方法apacheの国際化の方法は2種類あります。いずれの方法も、閲覧者から指定された"Accept-Languages"を参照して、適切な言語のファイルを自動的に返す仕組です。 | 方式 | 設定 | やり方 | 課題 | MultiViews | httpd.confに"Options MultiViews"を設定する。(通常デフォルト) | foo.htmlに対して、foo.html.en, foo.html.jaを配置する。foo.htmlは置かない。 foo.htmlにアクセスすることにより、言語に合わせたfoo.html.xxが返される。 | 直感的で簡単。 処理が遅いらしい。 foo.htmlを置けないのは気持ちが悪い(置くと直接アクセスでこちらが開かれてしまう) | Type-Map | httpd.confに "AddHandler type-map var"を設定する。(通常デフォルト) DirectoryIndex index.var index.php index.html 等と書く。 | foo.varを作成してfoo.en.html, foo.ja.htmlなどを配置する。foo.htmlは置かない。 foo.varにアクセスすることで、ファイル内に設定された適切な言語に合わせたファイルが返される。 | こちらの方法のほうが新しい。 処理が速いとのこと。 別ファイルを置かなくてはならないのはちょっと嫌。(もしくは、foo.htmlをvarファイルにすることも可能) |
特に理由がなければ、Type-Mapの方法を使うのが良さそうです。 実際の設定foo.htmlに対して、foo.varのファイルを作成して、URLをfoo.varでアクセスさせることになります。ファイルは以下のようなものです。 URI: foo.en.php Content-type: text/html Content-language: en
URI: foo.ja.php Content-type: text/html;charset=utf-8 Content-language: ja
そして、国際化したページ全てのページに対して xxx.var を作成し、リンクを全て xxx.var を差すように変更します。 なお、例えばindexファイルであれば、DirectoryIndex に index.varを追加しないとならなりません。(これは、httpd.confの設定を変更します。なお、phpのバージョンに依っては、php.confの設定も変更/削除しないとならないかもしれません) これでOKなのですが、実際に想定されている使い方とは少し違うようです。apacheのmanualディレクトリでType-Mapが使われているのですが、ここではindex.htmlをvarファイルにしています。動作させるためには、SetHandlerが必要になります。 <Directory "/var/www/manual"> <Files *.html> SetHandler type-map </Files> </Directory>
このようにすると、外部からのURLを変更する必要がありませんので、透過的に設定することができて便利です。要するに、htmlファイルと思ってアクセスすると実体はtype-mapファイルで、その設定に従って適切なファイルが実際には返されることになります。 但し、この設定をしてしまうと、単なるhtmlファイルとして扱うこと(中身がtype-mapでないtext/htmlであればそのまま返すなどということ)はできなくなります。これはサブディレクトリにも伝播してしまいますので、取り扱いには注意が必要です。
2010年 2月 13(土曜日)
朝日新聞サイト向け マーキー削除 II前回、ティッカー(marquee)を取り除いたのですが、今日から仕様が変更になったようです。以前は"TopBnr"というIDでしたが、"tickerAnchor"になったようです。そのため、ユーザスクリプトを修正しました。ここ(asahis.user.js)です。
|