プログラミングの最近のブログ記事

随分前に作った Linux(CentOS)上で動くプログラム。
LZH ファイルを解凍し、そのファイルをほげほげするのだが、さすがにもう Windows 上で LZH ファイルを作るのもきつくなってきた・・・ということで ZIP ファイル対応を依頼されたのだが(例えば、7-Zip なんかでも LZH 形式には対応してないからな(^^;)・・・ハマった(^^;

yum で入れた unzip は -O オプション(アーカイブ内のファイル名のエンコードが指定できる)が使える(以前のバージョンではパッチを当てないと駄目だった)ので、例えば unzip -Ocp932 -l exsample.zip で、exsample.zip の中の「かわいい中年男性一覧.csv」みたいなファイル名は取ってこれるんだけど、これをディスク上に解凍するとファイル名が化けまくる・・・

ちなみに、プログラムは EUC-JP で書かれている。なにせ、もう、18年前に初版公開したプログラムだからな(笑)。

これ、lha コマンドは EUC-JP でファイル名を出力するパッチが当たっていたので問題なかったんだけど、unzip は「サーバの locale のエンコードでファイル名が作られる」ため、実行サーバの locale である UTF-8 のファイル名となる。

他のプログラムにも影響あるから、なんとか EUC-JP でファイル名を設定してほしいなあ。

しかし、unzip にそのようなオプションはない。結局、unzip を実行する前に、locale を EUC-JP にする形でなんとかなった。

(例)
export LC_CTYPE=ja_JP.eucJP; /usr/bin/unzip -OCP932 -d /tmp exsample.zip

このプログラムは実行後シェルを閉じるので、LC_CTYPE の設定を投げっぱなしだが、同一シェル内で他のコマンドなど実行するのなら、

export LC_CTYPE=ja_JP.UTF-8

で locale を元の UTF-8 に戻すこと。

Tedious ドライバーを使って SQL Server に接続できない・・・」というエントリーに書いたように、Tedious ドライバーを使って JavaScript(node.js)からの SQL Server 2019 へのアクセスだけがうまくいかない。

C# で試しにコンソールプログラム作ってアクセスしてみたけど、やっぱりうまくいくなあ。

Program.cs はこんな感じ。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

dbaccess();

        static void dbaccess()
        {
            string dataSource, initCatalog, userId, passWord, connectionString;
            dataSource = "DBSERVER\\MSSQL2019";
            initCatalog = "InazumaDB";
            userId = "sa";
            passWord = "hogepass";
            connectionString = @"Data Source=" + dataSource + ";Initial Catalog=" + initCatalog + ";User ID=" + userId + ";Password=" + passWord;
            string query_s = "SELECT TOMO_ID, TOMO_NAME, TOMO_MEMO FROM InazumaDB.dbo.M_TOMODACHI";

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                //接続
                conn.Open();

                //SQL文を作成
                SqlCommand command_s = new SqlCommand(query_s, conn);

                using (SqlDataReader reader = command_s.ExecuteReader())
                {
                    //繰り返し読み込み
                    while (reader.Read())
                    {
                        //1~3列目の値を表示
                        Console.WriteLine(String.Format("{0}, {1}, {2}", reader[0], reader[1], reader[2]));
                    }
                }
                conn.Close();
            }
        }

これを実行すれば、コンソールに、

      1, アンモニア治郎, おごってくれるから友達
      2, クロム陽子, かわいいから友達
     14, ばいじん太郎, 事業に失敗したからそろそろ切ろう

こういう結果が表示される。

やっぱり、Tedious ドライバーが古くて SQL Server 2019 に対応していないってオチなんかね?

しばらく別件で忙しいので、調べている時間がないが・・・

Blazor で作成するのはだいたい企業内の Web システムだと思う。オープンなサイトをわざわざこんな糞重いフレームワークで組む人はいないだろう(よっぽどの Microsoft 好きか C# 以外の言語がまったくできない人を除いて).

今、Blazor で構築を予定しているサイトもやはり企業向けの Web サイトでログイン認証が必要である。
最初、自前でログイン画面を作って組み込んだ人のサイトなど見て勉強していたのだが、Blazor ってちゃんと「認証付きWeb画面」のテンプレートが用意されてるね。

新規プロジェクとで「Blazor WebAssembly アプリ」を選択。

20230804_blazer21.jpg

とりあえずテストなので、プロジェクト名などはそのままで「次へ」。

20230804_blazer22.jpg

そして、「認証の種類」で「なし」から「個別のアカウント」に変更して「作成」。
ちなみに、下に表示されていたチェック項目の「HTTPS用の構成」は「個別のアカウント」を選択すると消える。認証するときには SSL が前提なのでは選択不可(常に選択)になるということだろうなあ。

20230804_blazer23.jpg

これで作成されたプロジェクトを実行すると、画面の右上に「Login」というリンクが作成されている。

この Login をクリックしてみると、「フォームがモジュールにありません 読み込まれたモジュールに現在のスタック フレームが見つかりませんでした。この場所のソースは表示できません。」と VisualStudio 上に表示。そのまま処理を続けると、ブラウザの「There was an error trying to log you in: 'Network Error'」と表示された。

ま、なんの処理も書いてないからな(笑)

実際の認証処理作成の話はまた別のエントリーで。

Blazor WebAssembly は、JavaScript の代わりに C#言語を用いて Web アプリケーション(SPA(Single Page Application)ページ)を構築できるフレームワークだ(と書くと、小うるさい方々から色々指摘もありそうですが、まあ、いいのよ、いいのよ(笑))。

まあ、まあ、詳しくはググって識者の方のページを見てちょうだい。

で、とりあえず新規ページの追加方法のメモ。

まず、Pages の上で右ボタンメニューを展開。「追加」→「Razor コンポーネント...」を選択する。

20230804_blazer1.jpg

すると、新しい項目の追加画面が開くので、Razor コンポーネントを選択し、名前に新しく作成するファイル名(例では Login.razor)を入力し「追加」を行う。

20230804_blazer2.jpg

このとき、小文字で始まるファイル名、例えば login.razor などを指定すると、ビルドのとき、

RZ10011 Component 'login' starts with a lowercase character. Component names cannot start with a lowercase character.

ってエラーが出る。「コンポーネント名を小文字で始めることはできません。」ということなので、Login.razor に名前を修正する。

20230804_blazer3.jpg

これで、空の Razor コンポーネントファイルが作成されるので、あとは処理を書いていくだけ。

識者の方がたまたまこのエントリーを目にして、親切に指導していただけますように・・・という願いを込めて・・・

いや、実は Tedious ドライバーを使って、node.js で SQLServer に接続しようと思ってます。複数インスタンスが存在しているサーバに対して、下記のような JavaScript を書いて node.js で実行してみます。

    var Connection = require('tedious').Connection;  
    var config = {  
        server: 'DBSERVER\\MSSQL2019',
        authentication: {
            type: 'default',
            options: {
                userName: 'sa',
                password: 'hogepass'
            }
        },
        options: {
            encrypt: true,
            database: 'InazumaDB'
        }
    }; 

    var connection = new Connection(config);  

    connection.on('connect', function(err) {  
        if (err) {
            console.log(err);
        } else {
            console.log('CONNECT');
        }
    });

    connection.connect();

しかし、エラーに。接続できない・・・

C:\Users\shinoda>node connect_test.js
ConnectionError: Failed to connect to DBSERVER\MSSQL2019:1433 - getaddrinfo ENOTFOUND DBSERVER\MSSQL2019
    at Connection.socketError (C:\Users\shinoda\node_modules\tedious\lib\connection.js:1404:28)
    at C:\Users\shinoda\node_modules\tedious\lib\connection.js:1185:14
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  code: 'ESOCKET',
  isTransient: undefined
}

ただ、コマンドプロンプトから sqlcmd で接続すると、特に問題なく接続できるので、サーバ側の問題だとか、ホスト名が引けてないんだろうとか、そういう根本的な問題ではないと思うんですよねえ。

C:\Users\shinoda>sqlcmd -S DBSERVER\MSSQL2019 -U sa -P hogepass -d InazumaDB
1> SELECT * FROM M_TOMODACHI;
2> GO
TOMO_ID TOMO_NAME             TOMO_MEMO                              
------- --------------------- ---------------------------------------
      1 アンモニア治郎        おごってくれるから友達
      2 クロム陽子            かわいいから友達
     14 ばいじん太郎          事業に失敗したからそろそろ切ろう

(3 行処理されました)
1>

こんな感じで、sqlcmd ならバッチリです。

うーん、何がいけないんでしょうねえ。Tedious ドライバー自体はこの間最新版を入れたつもりですが・・・

C#.NET で音楽再生をしようと思うと、サードパーティー製のコントロールを使う方法などいくつかあるようだが、俺が今作っている「別所畑盆踊り大会専用プレーヤー Born Audrey(ボーン・オードリー)」では WindowsMediaPlayer コントロールを使っている。

インスタンスを作って、controls.play()メソッドで再生を始め、controls.stop()メソッドで再生をやめる。実に簡単。

しかし、困ったことに、WindowsMediaPlayer コントロールには「再生終了」のイベントがない。

MediaPlayer クラスで組めば MediaEnded イベントで再生終了後の処理が書ける。
状況が変化したら、MediaChange イベントが発生するので、そこでステータスが終了かどうかを見ることができる・・・という噂も聞いたが、これは本当かどうか確認してない(笑)

まあ、どちらにしても、MediaPlayer クラスで組めば再生終了は捉えることができるのである。

・・・が、悲しいことに、上に書いたように WindowsMediaPlayer コントロールを使うと「再生終了」のイベントはないので、自力でチェックするしかないのである。

どうするかというと、再生開始時にタイマーを使った定期的な監視処理をスタートさせ、終了のステータスを拾うしかない。

gTimer が Timer のインスタンスで、gWmp が WindowsMediaPlayer のインスタンスだとすると、

gTimer1.Interval = 1000; // 1秒ごとに処理を行う
gTimer1.Elapsed += Timer_Elapsed; // Elapsedイベントハンドラーを設定

とタイマーを設定し、ボタンが押されるなどして再生が開始されるとき、

gWmp.settings.volume = 20; // ボリュームを 20に
gWmp.URL = filepath.Text; // MP3ファイルのパスを設定
gWmp.controls.play(); // 再生開始

gTimer1.Start(); // タイマーを開始

という具合に、1秒毎に Timer_Elapsed を実行する。そして、Timer_Elapsed の中で、

private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (gWmp.playState == WMPPlayState.wmppsStopped) { // WMP のステータスが「終了」?
gTimer1.Stop(); // タイマーを終了
<再生終了時の処理>
}
}

これで、プレイリストに沿って連続で曲を再生する処理が書けるね。
急に、CGI から送信していたメールが、Gmail に届かなくなった。

maillog を確認すると、

550-5.7.26 This mail is unauthenticated, which poses a security risk to the
550-5.7.26 sender and Gmail users, and has been blocked. The sender must
550-5.7.26 authenticate with at least one of SPF or DKIM. For this message,
550-5.7.26 DKIM checks did not pass and SPF check for [www.exsample.co.jp] did
550-5.7.26 not pass with ip: [210.XXX.XXX.XXX]. The sender should visit
550-5.7.26  https://support.google.com/mail/answer/81126#authentication for
550 5.7.26 instructions on setting up authentication.

ああ、定番の 550 エラー・・・

www.exsample.co.jp ドメインの SPF レコードに、送信元サーバの IP 210.XXX.XXX.XXX が登録されていないので、怪しいからメールは受け取らんよ・・・という Gmail のエラー。

最近、セキュリティ対策でサーバ構成が変わって、www.exsample.co.jp が別のサーバの CNAME になったので(お客さんのサーバなので、俺の権限ではどうしようもない)、www.exsample.co.jp の TXT レコードが書けんのよね。なので、その中に SPF レコードも書けず・・・

ちなみに、example.co.jp の SPF レコードは存在する。

# nslookup -q=TXT exsample.co.jp

と引いてみると、

exsample.co.jp   text = "v=spf1 ip4:202.XXX.XXX.XXX ip4:163.XXX.XXX.XXX ip4:210.XXX.XXX.XXX include:spf.securemx.jp ~all"

と返ってくる。
エンベロープ From を admin@www.exsample.co.jp から admin@exsample.co.jp に変えてやればいいというわけだ。

CGI の中では、

    $mail_data = <<EOM;
From: admin@exsample.co.jp
To: hogehoge@gmail.com
Subject: TEST MAIL
MIME-Version: 1.0
Content-type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit
X-Mailer: CGI-Script

test death.

EOM

    if (!open (MAIL,"| sendmail -t")) {
        print "Error : Mail Send\n";
    }

    print MAIL $mail_data;
    close(MAIL);

のように sendmail でメール送信をしている。

ここで、sendmail の引数に -f を追加してやれば、エンベロープ From を書き換えることができる。

具体的には、↓こう。

    if (!open (MAIL,"| sendmail -t -f admin@exsample.co.jp")) {
        print "Error : Mail Send\n";
    }

これで、エンベロープ From には admin@exsample.co.jp がセットされ、Gmail は example.co.jp の SPF レコードを参照し、万事解決である(笑)
今更だが、初めて知った。

Oracle で、DB(データベース)サーバの持っている日時だけ取得する場合、

SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD HH24:MI:SS') AS dt FROM DUAL;

とかすると、dt という項目に、例えば 2023/04/06 10:31:46 という値(現在日時)がセットされて返ってくる。

プログラマじゃない人に説明すると、SELECT 文は DB 上のテーブル(データが書き込まれている表)から値を取得する命令で、FROM のあとにテーブル名を指定する。
例えば、「個人情報」というテーブルに皆の名前や住所、年齢などが記録されているとして、その中から名前と年齢を取得しようとすると、

SELECT 名前, 年齢 FROM 個人情報

とすれば名前と年齢の一覧が取得できる。

しかし、上に書いたように、その DB サーバ自体が持っている日付(システム日付)を取得したいときなどは、なにかのテーブルにその値が記録されているというものではないので(「今何時?」って聞かれて、あなたは手帳を開いて今の時間を確認するかい?」ってことだね)、テーブル名を指定するわけにはいかない。でも、SELECT 文はテーブルを読む命令だから、なんかテーブルを指定してもらわないと・・・というのが Oracle の考えなわけで、仕方ないから「じゃあ、なんかダミーのテーブルを指定しますわ」ってものが DUAL なんですな。ダミーテーブルね。

で、さっき、SQL Server 触ってて、「DBサーバのシステム時間取りたいけど、ダミーテーブル名ってなんだっけ?」としばし悩んだのである。「DUAL」じゃ「オブジェクト名 'DUAL' が無効です。」って怒られるし。

そんで、ググったら、SQL Server にはダミーテーブルなんて概念ないので、From を省略すればいいだけと。

SELECT SYSDATETIME() AS dt

で、dt という項目に 2023-04-06 16:54:39.9673299 って値がセットされるって・・・

MySQL や PostgreSQL も一緒で、(ただし、SYSDATETIME という組み込み関数ではなく)

SELECT NOW() AS dt

のように指定する。

「そうなんかあ・・・初めて知った。SELECT 文に FROM がないのは気持ち悪いな」・・・と、Oracle 世代(DBと言えば Oracle だったおっさん世代)として素直に思ったが、さっき、PostgreSQL を DBMS とした自分の過去のプログラムを見たら、普通に「SELECT NOW() AS サーバ日時」とか書いてて、「初めて知ったわけではなく、完全に忘れていたのか」と加齢の恐ろしさに愕然としている(^^;;;

いや、もちろんジョークソフトよ。

というか、俺、地元の盆踊り大会の放送係やってたんだけど、今まで盆踊りの曲をカセットテープで再生していたのよね。差し替えながら。これがもう職人技で俺には無理・・・ということで、曲をデジタルデータ(MP3)として取り込んで、MacBook 上の iTunes で再生しようと思ってたんだけど、どうせなら(少々PC操作が苦手な人でも使えるような)うちの盆踊り専用の再生アプリを作ろうかと・・・

でも、C#.NETで音楽流すようなアプリ作ったことないから、これからいろいろ調べつつ形にしていこうかなと。
どうも、Windows Media Player コントロールで実装するのが一番簡単なようだ。

というわけで試しに今作ってみたのが「周東音頭専用プレーヤー BornAudrey」である(笑)

20220623_player1.jpg

ただ「周東音頭」を再生することしかできない「いかした」音楽プレーヤーである。

ソースはこんな感じ。

using System.Windows.Forms;
using WMPLib;

namespace BornAudrey
{
    public partial class Form1 : Form
    {
        WindowsMediaPlayer _wmp = new WindowsMediaPlayer();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            _wmp.settings.volume = 20;
            _wmp.URL = @"C:\Users\shi\周東音頭.mp3";
            _wmp.controls.play();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (_wmp.playState == WMPPlayState.wmppsPlaying || _wmp.playState == WMPPlayState.wmppsPaused)
            {
                _wmp.controls.stop();
            }

        }
        }
}

さて、これをベースに機能追加していくぞ。あくまで我が地元盆踊り大会専用アプリとして(笑)

JavaScript の中で、Shift_JIS で書かれたテキストファイルを読んであれこれしてるんだけど、ファイルを読み込んでくる function はこんな感じ。
XMLHttpRequest を使っている。

//@cc_on
function getTextFile (fname) {

    var text = "";
    var ajax = new XMLHttpRequest();

    with (ajax) {
    /*@if(1) onreadystatechange @else@*/ onload /*@end@*/ =
    function () { readyState == 4 && status == 200 && (text = responseText); };
        open('GET', fname, false);
        overrideMimeType('text/html;charset=Shift_JIS');
        send(null);
    };

    return text;

}

fname には、http://www.exsample.jp/data/hoge.txt のような URI を渡してやる。
ただ、この hoge.txt を更新したあとも、ひたすら古いキャッシュの内容を読み込んでくるので、更新した内容が取れないという不具合が・・・

さて、どうしたらキャッシュを読まないようにできるのか???

答えは、

setRequestHeader('Pragma', 'no-cache');
setRequestHeader('Cache-Control', 'no-cache');
setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');

こんな風にリクエストヘッダにキャッシュじゃなくて生データ送ってこいと指示するだけ。
昔はよくサーバの設定を確認したりするのに、telnet で 80番ポートに接続し、

GET / HTTP/1.1
Host: www.exsample.com
User-Agent: handPower
Pragma: no-cache
Cache-Control: no-cache
Accept: */*

とかやってたのに、パッと思いうかばないもんだな(^^;

というわけで、

//@cc_on
function getTextFile (fname) {

    var text = "";
    var ajax = new XMLHttpRequest();

    with (ajax) {
    /*@if(1) onreadystatechange @else@*/ onload /*@end@*/ =
    function () { readyState == 4 && status == 200 && (text = responseText); };
        open('GET', fname, false);
        overrideMimeType('text/html;charset=Shift_JIS');
        setRequestHeader('Pragma', 'no-cache');
        setRequestHeader('Cache-Control', 'no-cache');
        setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');
        send(null);
    };

    return text;

}

という具合に修正すればOK。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはパソコンです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら