[CakePHP] 指定したグループを検索して表示する

findAllByAreaGroupId を使って、検索して取得するパターン

■Model

find 系を使うので特に変更なし

■Controller

/Store?group=10 でアクセスできるようにコントローラを作成

in Controller/StoreController.php

class StoreController extends AppController {
	public function index() {
		$id = null;
		$group = null;
		if (isset($this->params['url']['id'])) $id = $this->params['url']['id'];
		if (isset($this->params['url']['group'])) $group = $this->params['url']['group'];

		if ( $id != null ) {
			$this->set('Store',$this->Store->findAllById($id));
		} else if ( $group != null ) {
			$this->set('Store',$this->Store->findAllByAreagroupid($group));
		} else {
			$this->set('Store', $this->Store->find('all',array('limit'=>'100')));
		}
	}
}

パラメータで取得するので、$this->params[‘url’][‘group’] を使う。isset チェックを忘れずに。
PK ではないので、数百行返される可能性もあるので、limit をつけたほうがベターかも。

findAllByAreagroupid のところは、findAllByAreaGroupId のように書きたいところだが、このように書くと「area_group_id」のように展開されてしまうので「Areagroupid」のようにすべて小文字で書く。

limit を使おうとすると、conditions を指定しないとだめで、

$this->set('Store',
	$this->Store->find('all',
		array(
			'limit' => 10,
			'conditions' => array('AreaGroupId'=>$group))));

な風に長くなる。なんらかのヘルパー関数が必要かも…って慣れれば大丈夫?

■View

戻り値は配列になるので、foreach のところはそのままで。

■結果

指定したグループの最初の10件が表示されている(conditions を使ったパターン)。

カテゴリー: CakePHP | [CakePHP] 指定したグループを検索して表示する はコメントを受け付けていません

[CakePHP] 指定したIDの行だけ表示する

findById とか findAllById を使って PK の id を使って取得するパターン

■Model

find 系を使うので特に問題なし

■Controller

/Store?id=10 でアクセスできるようにコントローラを作成

in Controller/StoreController.php

class StoreController extends AppController {
	public function index() {
		$id = null;
		if (isset($this->params['url']['id'])) $id = $this->params['url']['id'];

		if ( $id != null ) {
			$this->set('Store',$this->Store->findAllById($id));
		} else {
			$this->set('Store', $this->Store->find('all',array('limit'=>'100')));
		}
	}
}

パラメータの部分は、$this->params[‘url’][‘id’] のように取得できる。
ただし、?id=10 の部分が指定されていない場合は、エラーになるので、isset でチェック。

findAllById を使っているのは、配列を返したいから。findById にすると単体になるんだけど、View の foreach を変更する必要があり「面倒なので」、1件の場合も配列にするため。

■View

findAllById を使っているので、foreach のところは書き換えずに済む。

■結果

指定した ID の 1件だけが表示される。

カテゴリー: CakePHP | [CakePHP] 指定したIDの行だけ表示する はコメントを受け付けていません

[CakePHP] 最初のひな形を作る

Store というテーブルがあるとして、

■Model

テーブル名を複数形にしたくない(既存のテーブル名を変えたくない)ので、$useTable で指定

in Model/Store.php

class Store extends AppModel
{
	var $useTable = 'Store';	// 対象テーブル名を指定
}

find 系の関数だけを使うのであれば、最初は空で ok 。

■Controller

/Store でアクセスできるようにコントローラを作成

in Controller/StoreController.php

class StoreController extends AppController {
	public function index() {
		$this->set('Store', $this->Store->find('all',array('limit'=>'100')));
	}
}

全件検索すると1万行ぐらいあるので、最初の100行で打ち切り。find でリミットを指定できるのだけど、ここは「安全」にコーディングするために、find100 とかいうメソッドでアクセスしたほうがよいかも。

class StoreController extends AppController {
	public function index() {
		$this->set('Store', $this->Store->find100());
	}
}

この場合は、Model/Store.php に find100 メソッドを追加する。

class Store extends AppModel
{
	var $useTable = 'Store';
	
	function find100()
	{
		$sql = <<< HERE
SELECT
	ID, AreaGroupID, Name
FROM Store
limit 0,100
HERE;
		return $this->query($sql);
	}
}

こうやって SQL 文を直書きする。

■View

Controller で設定 $this->set(‘Store’,…) したところが使えるので、そのまま foreach でループさせる。

in View/Store/Index.ctp

store index の表示
<h2>Store list</h2>
<table>
	<tr>
		<td>id</td>
		<td>areagroupid</td>
		<td>name</td>
	</tr>

<!--
<?php print_r( $Store ); ?>
-->

<?php foreach($Store as $item) : ?>
	<tr>
		<td><?php echo $item['Store']['ID'] ?></td>
		<td><?php echo $item['Store']['AreaGroupID'] ?></td>
		<td><?php echo $item['Store']['Name'] ?></td>
	</tr>
<?php endforeach ; ?>

■結果

先頭の 100 件だけ表示する。

カテゴリー: CakePHP | [CakePHP] 最初のひな形を作る はコメントを受け付けていません

[CakePHP] CakePHP 再々入門

とある業務のフレームワーク選定として、
・独自MVC
・CakePHP
・ActiveRecord
を考えていたのですが、ひとまず、CakePHP で試験的に実装をしてみることに「決断」。まあ、試験的なので、業務として使えるパーツを集めるということで、

  • CRUD をひと通り
  • Web API として URL からパラメータを取得
  • 複雑なクエリ
  • トランザクションの制御
  • フォーム認証関係(ACL)、セッション情報

をがつがつと試していきます。

http://cakephp.jp/ の 2.3.4 を使ってチェック。

CakePHP 再入門(1) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1711

からの一連の記事は、SELECT 関係しか調べていないので、ちょっと今回の業務ロジックには足りない…ということでスピードをあげて調べ直し。システム構成としては、クライアント側に C# で Windows アプリ、サーバーに PHP+MySQL のレンタルサーバーという構成で、おそらく平均的な C/S 構成を作ったときに「ランニングコスト&保守を含めて」コストパフォーマンスが良いパターンかと。最近は仮想サーバーにしても IIS で安くするものですが、「プログラマ単価」的に PHP が安いとか、クライアントプログラミング的に C# が融通が利くとか、そのパターンです。まあ、今回は「プログラマ単価」は関係ないのですが、Linux サーバーに PHP で Web API を作って貰えるパターンがあると流用できると思います。

# 4年ぐらい前だったら、Silverlight+PHP のパターンを考えるのですが、Windows 8 を見ると、ちょっとね…という具合なので、WPF(C#)+PHP の組み合わせ。

C/S 通信には、SOAP, Web API(REST), XML-RPC というパターンがあります。Wordpress との連携を考えると、XML-RPC のほうがベターなのですが、C# での XML-RPC のサポートがいまいちなのと(xml-rpc.net しかない)、生 PHP でアクセスする場合には、そのメリットが少なくなってしまうので、REST で。C# 寄りにして SOAP を使う方法もあるのですが、今回は PHP に負担を掛けない方法で。

■目次

そんな訳で、以下は CakePHP 再々入門の目次(場所の予約)

カテゴリー: CakePHP | [CakePHP] CakePHP 再々入門 はコメントを受け付けていません

[C#] Log4netを使うサンプル

ざっと書き下しておきます。

Apache log4net: Home
http://logging.apache.org/log4net/
Log4NET – TSCWiki
http://www.tokyosoft.co.jp/tscwiki/index.php/Log4NET
【C#】【VS2010】Visual Studio 2010で Log4net を使う – プログラム の個人的なメモ – Yahoo!ブログ
http://blogs.yahoo.co.jp/dk521123/20264938.html

■環境構築

Visual Studio 2012 と 64bit という環境なので、これで。

Download Apache log4net
http://logging.apache.org/log4net/download_log4net.cgi
から、log4net-1.2.11-src.zip のほうをダウンロードして、展開。
D:toolslog4net-1.2.11 へコピー。
D:toolslog4net-1.2.11srclog4net.vs2010.sln を開く。

対象のフレームワークを「.NET Framework 4.5」に変更

image

ビルドをすると

D:toolslog4net-1.2.11buildbinnet2.0debuglog4net.dll

に作成される。

■ログ出力のサンプル

適当なプロジェクトを作って、参照設定で D:toolslog4net-1.2.11buildbinnet2.0debuglog4net.dll を追加。

image

app.config を開いて、Log4NET – TSCWiki にある「プロジェクトの組み込み」の部分をそのまま組み入れ。
作成するログファイル名だけを

<param name="File" value="D:toolslog4net-1.2.11outlog-file.txt" />

のように書き換えておく。

出力設定自体は、

    <root>
      <!-- ここではINFOログのレベルを指定 -->
      <level value="DEBUG" />
      <!-- 下記の何れかを使用
      <level value="OFF " />
      <level value="FATAL" />
      <level value="ERROR" />
      <level value="WARN" />
      <level value="INFO" />
      <level value="ALL" />
      -->
      <!-- どのログ出力先を使用するか -->
      <appender-ref ref="LogToFile" />
      <appender-ref ref="DayRollingLogToFile" />
      <!-- 複数指定可能 -->
    </root>

のように、通常のログ「LogToFile」と、日付でローリング「DayRollingLogToFile」で出力。

ソリューションエクスプローラーで「AssemblyInfo.cs」を開いて、

image

[assembly: log4net.Config.XmlConfigurator(Watch = true)] の記述を追加する。

// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
// アセンブリに関連付けられている情報を変更するには、
// これらの属性値を変更してください。
[assembly: AssemblyTitle("SampleLog4Net")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SampleLog4Net")]
[assembly: AssemblyCopyright("Copyright c  2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: log4net.Config.XmlConfigurator(Watch = true)]

適当に画面を作って、

image

namespace SampleLog4Net
{
	public partial class Form1 : Form
	{
		private static readonly log4net.ILog log =
		log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		public Form1()
		{
			InitializeComponent();
		}

		private void button1_Click(object sender, EventArgs e)
		{
			log.Info("INFOで出力");
		}
	}
}

な風に書く。log4net.ILog log なところをプライベートフィールドで。

■サンプルを実行してみる

こんな風に時刻とクラス名、ログが出ます。

image

出力自体は、app.config に書いた

      <level value="ERROR" />
      <level value="WARN" />
      <level value="INFO" />
      <level value="DEBUG" />

のところで切り替えます。通常のデバッグ時には、DEBUG を有効にしておいて、リリース時には、ERROR だけを有効にするとか規約で決めればよいでしょう。

■動的にログ出力レベルを変更する

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

を設定することで、実行時にも *.config を変えることができる…という記事があるのですが、.NET 4.5 だと動きませんでした。なので、直接ソースの中から操作します。

var rep = log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy;
rep.Threshold = log4net.Core.Level.Info;

ソースを見ると、出力レベルの判断は、Hierarchy プロパティで判断しているのでこれを書き換えます。この例では、Info 以上の出力(DEBUG出力は出ない)設定にしています。これはグローバル変数なので、全体に影響しますね。

■出力時のファイル名と行番号を出力する

こんな感じで拡張メソッドを作っておけばOK。

VS2012 の場合は、CallerMemberName 属性を使って生成、VS2010 の場合は StackFrame クラスを使って生成します。ここでは、Debug しか作っていませんが、同じように Info とかを作ればよいし、適当にフォーマットすれば使えるかと。

public static class ILogEx
{
    /// <summary>
    /// VS2012の場合
    /// </summary>
    /// <param name="log"></param>
    /// <param name="msg"></param>
    /// <param name="name"></param>
    /// <param name="path"></param>
    /// <param name="num"></param>
    public static void DebugL(this log4net.ILog log, string msg,
        [CallerMemberName] string name = "",
        [CallerFilePath] string path = "",
        [CallerLineNumber] int num = 0)
    {
        log.Debug(string.Format("{0} {1} {2} {3}", name, path, num, msg));
    }
    /// <summary>
    /// VS2010の場合
    /// </summary>
    /// <param name="log"></param>
    /// <param name="msg"></param>
    public static void DebugN(this log4net.ILog log, string msg)
    {
        var sf = new StackFrame(1, true);
        string name = sf.GetMethod().ToString();
        string path = sf.GetFileName();
        int num = sf.GetFileLineNumber();
        log.Debug(string.Format("{0} {1} {2} {3}", name, path, num, msg));
    }
}

~ 追記
と思ったものの、もともと log4net にはログの形式で %location を使えばファイル名などが出力できるので、これで良いかと。
連載:VBで実践! 外部コンポーネント活用術:オープンソースのロギング・サービス「log4net」を使う (3/4) – @IT あたりを参考に

        <param name="ConversionPattern" value="%date [%thread] %-5level %logger - %location %message%newline" />

■サンプル

サンプルコードはこちら
SampleLog4Net-v0.1-src.zip をダウンロードしてください。

カテゴリー: C# | [C#] Log4netを使うサンプル はコメントを受け付けていません

[WinRT] Surfaceのフォトで選択した画像を、デスクトップに送る

[WinRT] デスクトップで閲覧しているURLをSurface RTに送る – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4718
[WinRT] Surface RT で閲覧しているURLをデスクトップに送る – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4723

送るシリーズの第3弾…って訳ではないですが、メモ的に。

Surface RTからURLをデスクトップに送る仕組みを応用して、画像ファイルを送ります。ここではWindows RT標準装備の「フォト」で共有した場合。他にも SkyDrive をやってみたいのですが、これは方式が違うのでまた別途ソースを書きおこします。内部動作が違っても、表面的な UI は同じほうがいいですからね。

■Surface から共有させる

マニフェストの宣言で「サポートされるファイルの種類」を増やしておきます。ここでは、画像ファイルしか扱わないので、まめに「.jpeg」とか書いていきます。「すべてのファイルの種類をサポートする」にチェックを入れてもokなんですが、なんかの理由付けがないと審査で落ちそうな感じがするし、まあ用途としては制限させたほうがいいので。

共有しているところの、ShareTargetPage.xaml.cs を書き換えます。
フォトからは StandardDataFormats.StorageItems を使って送られてくるので、これで受け取り。複数のファイルが送れるのですが、今回は最初の1枚だけを対象に。

public async void Activate(ShareTargetActivatedEventArgs args)
{
    this._shareOperation = args.ShareOperation;
...略
    if (this._shareOperation.Data.Contains(StandardDataFormats.StorageItems))
    {
        Debug.WriteLine("contain StorageItems");
        var files = await this._shareOperation.Data.GetStorageItemsAsync();
        foreach (StorageFile f in files)
        {
            Debug.WriteLine("name: {0}", f.Path);
        }
    }
}

画像転送は、HTTPのPUTを使ってBASE64で送ります。このあたりは先行きXMLシリアライズを使うつもりなので、ざっと書き下し。バイト配列とのコンバートは、慣れると…とはいえ慣れても面倒なのですが、この通りに各と動きます。

    private async void ShareButton_Click(object sender, RoutedEventArgs e)
    {
        this.DefaultViewModel["Sharing"] = true;
        this._shareOperation.ReportStarted();
        // TODO: this._shareOperation.Data を使用して共有シナリオに適した
        //       作業を実行します。通常は、カスタム ユーザー インターフェイス要素を介して
        //       このページに追加されたカスタム ユーザー インターフェイス要素を介して
        //       this.DefaultViewModel["Comment"]
...略
        if (this._shareOperation.Data.Contains(StandardDataFormats.StorageItems))
        {
            var files = await this._shareOperation.Data.GetStorageItemsAsync();
            var file = files[0] as StorageFile;
            // 画像データを転送
            var stream = await file.OpenReadAsync();
            using (var mem = new MemoryStream())
            {
                var rs = stream.AsStreamForRead();
                await rs.CopyToAsync(mem);
                byte[] data = mem.ToArray();
                string url = "http://mars:8090/PutImage";
                HttpClient cl = new HttpClient();
                string b64 = System.Convert.ToBase64String(data);
                StringContent cont = new StringContent(b64);
                var res = await cl.PutAsync( url, cont );
            }
        }
        this._shareOperation.ReportCompleted();
    }
}

c# – WinRT image handling – Stack Overflow
http://stackoverflow.com/questions/10013799/winrt-image-handling
DataPackageView.GetStorageItemsAsync | getStorageItemsAsync Method (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/windows.applicationmodel.datatransfer.datapackageview.getstorageitemsasync

このあたりを読んで、まめに実装するがよいかと。

■デスクトップで画像を受ける

Listener のタスクが長くなってきたので、別関数にくくりだし。Task の中からは GUI コントロールを直接さわれないので、Invoke を使います。いろいろやってこれが簡単な方法かと。
画像ファイルは、BASE64 からバイナリ配列に直して Bitmap を作るという手順です。このあたりの流れは、適度に隠蔽化してしまうと楽になると思います。

private void Form1_Load(object sender, EventArgs e)
{
    listener = new HttpListener();
    listener.Prefixes.Add("http://*:8090/");
    listener.Start();

    // 受信待ちタスク
    Task tsk = new Task( ListenLoop );
    tsk.Start();
}

async void ListenLoop()
{
    while (true)
    {
        var cont = listener.GetContext();
        var req = cont.Request;
        var res = cont.Response;
        if (req.RawUrl.StartsWith("/GetUrl"))
        {
            Debug.WriteLine(string.Format("req {0}", req.RawUrl));
            var tw = new StreamWriter(res.OutputStream);
            tw.Write(_text);
            tw.Close();
            res.Close();
        }
        else if (req.RawUrl.StartsWith("/PutUrl") == true)
        {
            Debug.WriteLine(string.Format("req {0}", req.RawUrl));
            var text = req.RawUrl.Replace("/PutUrl/", "");
            text = System.Uri.UnescapeDataString(text);
            // textBox1.Text = text;
            textBox1.Invoke((MethodInvoker)(() => textBox1.Text = text));
            var tw = new StreamWriter(res.OutputStream);
            tw.Write("OK");
            tw.Close();
            res.Close();
        }
        else if (req.RawUrl.StartsWith("/PutImage") == true)
        {
            // 画像をBASE64で受け取る
            Debug.WriteLine(string.Format("req {0}", req.RawUrl));
            var st = req.InputStream;
            var sr = new StreamReader(st);
            string b64 = await sr.ReadToEndAsync();
            byte[] data =  System.Convert.FromBase64String(b64);
            var mem = new MemoryStream(data);
            Bitmap bmp = new Bitmap(mem);
            // pictureBox1.Image = bmp;
            pictureBox1.Invoke((MethodInvoker)(() => pictureBox1.Image =bmp ));

            var tw = new StreamWriter(res.OutputStream);
            tw.Write("OK");
            tw.Close();
            res.Close();
        }
        else
        {
            var tw = new StreamWriter(res.OutputStream);
            tw.Write("ERROR");
            tw.Close();
            res.Close();
        }
    }
}

■動かしてみる

フォトから共有させて、

フォームアプリに転送するだけです。

そのままファイルに保存してもいいし、Clipboard.SetImage(pictureBox1.Image) な風にしてい、クリップボードに送るのもいいでしょう。

■サンプルソース

サンプルソースはこちら。
http://sdrv.ms/10mK1ch

カテゴリー: C#, WinRT | [WinRT] Surfaceのフォトで選択した画像を、デスクトップに送る はコメントを受け付けていません

[WinRT] Surface RT で閲覧しているURLをデスクトップに送る

[WinRT] デスクトップで閲覧しているURLをSurface RTに送る – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4718

の逆向きパターンで、タブレットPCからデスクトップのほうにURLを送ります。

こっちのほうは、比較的簡単?で、タブレットPCで動作しているブラウザ(IE10)から共有を使って、ストアアプリに送信、そのストアアプリからデスクトップアプリに通信させます。簡易HTTPサーバーを立てるところは、前回と同じなので流用してます。というか同じアプリに乗せています。

■簡易HTTPサーバー

///
/// フォームロード時
///
///
///
private void Form1_Load(object sender, EventArgs e)
{
    listener = new HttpListener();
    listener.Prefixes.Add("http://*:8090/");
    listener.Start();

    // 受信待ちタスク
    Task tsk = new Task(
        () =>
        {
            while (true)
            {
                var cont = listener.GetContext();
                var req = cont.Request;
                var res = cont.Response;
                if (req.RawUrl.StartsWith("/GetUrl"))
                {
                    Debug.WriteLine(string.Format("req {0}", req.RawUrl));
                    var tw = new StreamWriter(res.OutputStream);
                    tw.Write(_text);
                    tw.Close();
                    res.Close();
                }
                else if (req.RawUrl.StartsWith("/PutUrl") == true)
                {
                    Debug.WriteLine(string.Format("req {0}", req.RawUrl));
                    var text = req.RawUrl.Replace("/PutUrl/", "");
                    text = System.Uri.UnescapeDataString(text);
                    textBox1.Text = text;
                    var tw = new StreamWriter(res.OutputStream);
                    tw.Write("OK");
                    tw.Close();
                    res.Close();
                }
                else
                {
                    var tw = new StreamWriter(res.OutputStream);
                    tw.Write("ERROR");
                    tw.Close();
                    res.Close();
                }
            }
        });
    tsk.Start();
}

同じ8090ポートを使って、/GetUrl の場合はデスクトップからタブレットPCへ、/PutUrl の場合はタブレットPCからデスクトップへ通信をします。
まあ、先行きは XML シリアライズを使って実装しますが、ひとまずこれで使い勝手を確認。

■共有ターゲットの実装

ブラウザから「共有」の対象になるために、連携するストアアプリを共有ターゲットにします。
追加で「共有コントラクト ターゲット」を追加してから、以下の部分を修正。

□共有コントラクトを開いたとき

public async void Activate(ShareTargetActivatedEventArgs args)
{
    this._shareOperation = args.ShareOperation;
    // ビュー モデルを使用して、共有されるコンテンツのメタデータを通信します
    var shareProperties = this._shareOperation.Data.Properties;
    var thumbnailImage = new BitmapImage();
    this.DefaultViewModel["Title"] = shareProperties.Title;
    this.DefaultViewModel["Description"] = shareProperties.Description;
    this.DefaultViewModel["Image"] = thumbnailImage;
    this.DefaultViewModel["Sharing"] = false;
    this.DefaultViewModel["ShowImage"] = false;
    this.DefaultViewModel["Comment"] = String.Empty;
    this.DefaultViewModel["SupportsComment"] = true;
    Window.Current.Content = this;
    Window.Current.Activate();
    // ブラウザからURLを共有する
    if (this._shareOperation.Data.Contains(StandardDataFormats.Uri))
    {
        var uri = await this._shareOperation.Data.GetUriAsync();
        this.DefaultViewModel["Comment"] = uri.ToString();
    }
    // 共有されるコンテンツの縮小版イメージをバックグラウンドで更新します
    if (shareProperties.Thumbnail != null)
    {
        var stream = await shareProperties.Thumbnail.OpenReadAsync();
        thumbnailImage.SetSource(stream);
        this.DefaultViewModel["ShowImage"] = true;
    }
}

□共有コントラクトで Share をタップしたとき

private async void ShareButton_Click(object sender, RoutedEventArgs e)
{
    this.DefaultViewModel["Sharing"] = true;
    this._shareOperation.ReportStarted();
    // TODO: this._shareOperation.Data を使用して共有シナリオに適した
    //       作業を実行します。通常は、カスタム ユーザー インターフェイス要素を介して
    //       このページに追加されたカスタム ユーザー インターフェイス要素を介して
    //       this.DefaultViewModel["Comment"]
    if (this._shareOperation.Data.Contains(StandardDataFormats.Uri))
    {
        // テキストデータを保存
        var text = this.DefaultViewModel["Comment"] as string;
        string url = "http://mars:8090/PutUrl/" + System.Uri.EscapeUriString(text);
        HttpClient cl = new HttpClient();
        var res = await cl.GetAsync(new Uri(url));
    }
    this._shareOperation.ReportCompleted();
}

実は、Active がなくていきなり share してもいいんですが、まあ、テンプレートのままで使ってみます。
こうすることで、

  1. ブラウザで閲覧する。
  2. チャームで「共有」を開いて GoRTApp を選択する。
  3. URL を確認してデスクトップに URL を通知する。
  4. デスクトップアプリのほうで、URL をブラウザで開く。

という流れができます。

■サンプルソース

サンプルソースはこちらで。

仕組みはこんな感じ。

20130422_02

カテゴリー: C#, WinRT | [WinRT] Surface RT で閲覧しているURLをデスクトップに送る はコメントを受け付けていません

[WinRT] デスクトップで閲覧しているURLをSurface RTに送る

サブノート/サブディスプレイに Surface RT を使っていると、デスクトップで見つけた Youtube を Surface で表示したい時が頻繁にあります。この場合、どうやって送るかというと…

  1. Twitter でメモ的に流してから、Surface 側で拾う
  2. SkyDrive に URL を置いてから Surface で拾ってくる。
  3. ちまちまと URL を Surface で打つ

という方法があるわけですが、どれも面倒くさい。ただし、Windows 8 同士であれば、デスクトップアプリでさっくりとできそうなのですが、片方が Windows RT なものだから、Windows ストア アプリしないといけないという制限がある。

image

で、前々から考えていた、デスクトップPCのほうに簡易HTTPサーバー、タブレットPCにWindows ストア アプリのクライアント、というパターンで実装してみます。

■簡易HTTPサーバー

/// <summary>
/// フォームロード時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
    listener = new HttpListener();
    listener.Prefixes.Add("http://*:8090/url/");
    listener.Start();

    // 受信待ちタスク
    Task tsk = new Task(
        () =>
        {
            while (true)
            {
                var cont = listener.GetContext();
                var req = cont.Request;
                var res = cont.Response;
                Debug.WriteLine(string.Format("req {0}", req.RawUrl));
                var tw = new StreamWriter(res.OutputStream);
                tw.Write(_text);
                tw.Close();
                res.Close();
            }
        });
    tsk.Start();

}
HttpListener listener;
private string _text = "";

/// <summary>
/// ドロップされた時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_DragDrop(object sender, DragEventArgs e)
{
    var text = "";
    var obj = e.Data;
    foreach (var fmt in obj.GetFormats())
    {
        Debug.WriteLine( fmt );
        if (fmt == "text/uri-list")
        {
            var sr = (MemoryStream)obj.GetData("text/uri-list");
            text = Encoding.Unicode.GetString(sr.ToArray());
            break;
        }
    }
    textBox1.Text = text;
    _text = text;
}

private void Form1_DragEnter(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.All;
}

簡易サーバーは HttpListener で作れば ok です。今回はポート番号を 8090 にして待ちにします。URLはFirefox からドラッグ&ドロップします。何故かIE10の場合はできないのですが…まあ、自分専用なのでFirefoxで。これは後で調査。

■Surface RT 側のクライアント

image

自動的に取ってくるのがいいのですが、ひとまず、Get ボタンで URL を取得、その後 IE10 で開くという処理を続けてやります。

private async void GetClick(object sender, RoutedEventArgs e)
{
    string url = "http://mars:8090/url";
    HttpClient cl = new HttpClient();
    var res = await cl.GetAsync(new Uri(url));
    var text = await res.Content.ReadAsStringAsync();
    text1.Text = text;
    // 起動
    await Windows.System.Launcher.LaunchUriAsync(new Uri(text));
}

■サーバーの設定

実は Winodows 8 では、ポートの制限が厳しくて、単純に HttpListener を作っただけでは動きません。管理者のコマンドラインから、次のコマンドを実行します。

 netsh http del urlacl url=http://*:8090/ -user=masuda 

Community Open Day 2012 の補足その1 – Moonmile Solutions Blog
Community Open Day 2012 の補足その2 – Moonmile Solutions Blog
に、netsh の使い方が書いてあります。

あとは、ファイアーウォールで、8090 のポートを開けることです。ポート絡みがややこしいので、本格的にツールにするとしたらインストーラが必須ですね。

■で、何につかうのか?

iSteve from Justin Long, Jorge Garcia, James_Urbaniak, Michaela Watkins, Steve Tom, John Ross Bowie, Paul Rust, Ryan Perez, Juzo Yoshida, Brian Huskey, Art Evans, jill_donnelly, Anthony Gioe, Ally Hord, iSteve, Funny Or Die, Charles Ingram, NickCorirossi, Joe Farrell, and Katy Walker を Surface に送ってみたかったんですよ…って、動かしてみたら、ああ、Surface RT の IE10 だと Flash が動かないタイプな模様 orz 、結局デスクトップPCで閲覧

■サンプルソース

サンプルソースはこちら。適当にホスト名とかポート番号とかを変えて使ってください。

カテゴリー: WinRT | [WinRT] デスクトップで閲覧しているURLをSurface RTに送る はコメントを受け付けていません

Frameworks Round 2 に ASP.NET系を含めてみたい

@neuecc さんのツイートの「Cakeは…」から辿った Frameworks Round 2 – TechEmpower Blog を眺めてみていくつかメモ。

http://www.techempower.com/blog/images/article/framework-benchmarks-round2/json-i7.png

正直「CakePHPがこんなに下位≒遅いなんて信じられない~」かつフレームワーク選定を考え直さないといけないなと思いつつ、が調べていった発端なので、ちょっと視点がずれているかもしれませんが。まあ、個人の感想ということで。

ちなみに、ベンチマークのソースコードは TechEmpower/FrameworkBenchmarks · GitHub でダウンロードできます。Issues · TechEmpower/FrameworkBenchmarks · GitHub を見ると、結構な量のコメントがあるので「釣り」としてうまく機能しているかなと。それぞれ、自分の使っているフレームワークを心理的に擁護したいので、続々とコメントが集まって来ています。そういう自分も釣られた一人。ただ、こういう絨毯爆撃的なベンチマークはあまりないので(Oracle にはベンチマークをしちゃダメという契約条項もあるし)さっくりと比較記事的に面白いし、相対的に色々なコードを眺めるきっかけになります。netty は前から知っていたのですが、こういう風に比較をすると Java コードであっても無視できないですよね~、とか。

では、30分ぐらいでざっと書き下します。以下、主に Database access test (multiple queries) を見て比較しています。

■ASP.NET 系が入っていない問題

意図的…というよりも、Amazon EC2 上と Ubuntu 上になるので、.NET 系が入っていません。mono で動かせば?というのがコメントとかにありましたが、ここはやっぱり IIS 上で再実験してみたいところです。気になるポイントは、

  • netty 風に .net で非同期/単体サーバープログラムを作った場合どうなるのか?
  • ASP.NET MVC がどの位置になるのか?
  • ASP.NET Web API がどの位置になるのか?

リクエスト数の絶対値ではなくて、相対値を調べることになるので、PHP まわり、Ruby まわりも IIS 上で動かしてみる必要がありかと。xampp をインストールして、べたな状態で動かすのもありですね。java のほうは詳しくないので、Spring とか Hivernate とか動けばokかと。

■servlet-raw と php-raw がほぼ同じ

ソースコードを見ると php-raw と php の違いは、直接 PDO を使うか、ActiveRecord というライブラリを使うかの違いですかね?(ActiveRecordをよく知らない)。ActiveRecord は MVC っぽい作りなので、べたで作れば PDO を使うし(Wordpress は mysql_query を使うから遅いという話もあるし)ってことで、java の servlet と同じスピードが出るのであれば、さっくりと PHP で組むのもありかな?と思ったり。乱暴ですが。

ただし、フレームワークベンチな訳で、spring が servlet-raw よりもあまり遅くなっていないのはいいですよね。

■コネクションプーリングの問題

設定をみると CakePHP のコネクションプーリング?(persistent)の設定がoffになっているし、そもそも PHP って MySQL に対してプーリングができるのかどうかが怪しいし、そういう点ではコンパイル系の Java のほうがマルチコネクションに対しては断然有利です。今回のベンチマークのように、小さいクエリを何度もクライアントから発行する場合は、サーブレットで DB に接続する時間がほとんどを占めてしまうので、java と php では大きな差が出る可能が…と言いつつ、servlet-raw と php-raw の差があまりないので、そうでもないのかも。このあたりは自分で再実験をしてみたいところです。

■MVC の問題

MVC パターンを噛ますと、当然クラスロードやらなんやらで遅くなっていしまうのが当然で、このあたりは、CakePHP、Rails は不利ですよね。struts が入っていないのは意図的?最近は使われない?

なので、この中に ASP.NET MVC、ASP.NET Web API がどの位置になるのかが興味あるところです。MVC を入れるのは、瞬間的なパフォーマンスよりも、継続的プログラミングのパフォーマンスや、ローンチのパフォーマンスを上げたいってところが主なので、そこで実行時のパフォーマンスとのトレードオフがあっても然り。

■Apache と IIS の問題

Ubuntu なので、Apache を使っていますが、Windows Server にすれば、IIS+PHP+MySQL とか、IIS+PHP+SQL Serverとか、IIS+ASP.NET+SQL Server とか諸々の組み合わせがでてきます。フレームワークの比較からは外れますが、Windows Server 上で動かした場合(Azure 上で動かした場合)は、このフレームワークランキングって変わるのか?ってのが興味あるところです。

# ざっくりと、ASP.NET Web API はチェックしたい。

カテゴリー: 開発 | 3件のコメント

MySQLでMySqlBulkLoaderのスピードをHDD/SSDで比較する

SSDにするとBlukcopyは5倍ぐらい早くなる – Moonmile Solutions BlogMySQLでHDD/SSDのアクセススピードを比較する – Moonmile Solutions Blog の続きです。@h141gm さんから、MySqlBulkLoader もあるよ、ということなので再実験。さすがに、insert の連続では SQL Server との比較が無理があるし、insert の連続が SSD の処理に追い付いているかどうかもあやしいので、ちょっと仕切り直します。

# my.ini の値は、xampp の吊るしのまま使っています。ひとまず既製品状態でどのくらいの差がでるのかな?ってことで。チューニングするとどの位なのか?ってのも興味あるところなのですが「チューニングしなくても、SSD を換装すれば「手軽に」早くなる、ってのが理想的なのです。

例によって、100万件のアクセス生ログを読み込せます。

■MySqlBulkLoader で insert する

SSD の BulkLoader で、11.8 sec

image

image

HDD で 26.8 sec

image

image

SSD のほうが HDD よりも 2.5 倍ぐらい早くなっています。

BulkCopy/BulkLoader SSD HDD
SQL Server 7.8 sec 32.1 sec
MySQL 11.8 sec 26.8 sec

SQL Server の BulkCopy は、メモリ上の DataTable から一気に突っ込むので、ファイルを解析しながロードする MySQL の BulkLoader よりも早いのは当然…なのですが、ええ、HDD のほうが逆転しているけど無視で(苦笑)。ロードするファイル自体は SSD に置いてあるので、このファイルが HDD 上にあると MySQL の BulkLoader のほうが遅くなる可能性があります。この計測からいけば、単純に HDD から SSD に切り替えただけでも、2.5 倍から 4 倍ぐらいの効果がありますね、ってことが分かります。

■500万件のデータから SELECT する

SQL Server のデータベースファイルを SSD に移す – Moonmile Solutions Blog と似たようなクエリを使って計測します。

初回の SELECT が、SSD で 5.1 sec、HDD が 7.9 sec

imageimage

2回目以降はキャッシュが効くのかと思っていたら、SSD で 5.0 sec、HDD で 7.9 sec と変わりません。このあたりはチューニングで変わるのかもしれません。

imageimage

SSD初回 SSD二回目 HDD初回 HDD2回目
SQL Server 4.6 sec 0.4 sec 15.1 sec 0.4 sec
MySQL 5.1 sec 5.1 sec 7.9 sec 7.9 sec

使用メモリを見ると、SQL Server は2回目のときに 4GB 程度取っていますが、MySQL の場合は 36 MB 程度と謙虚です。がっつりメモリを割り当ててやれば、MySQL の場合も超早になるのかなと。ただし、SQL Server + HDD の組み合わせが遅すぎですよね…MySQL が早いのかもしれませんが。

この結果を見ると、SELECT の場合は、HDD から SSD に換装してもあまりメリットがないかな、って感じもします。この場合は、単純なアクセスログでワンテーブルの検索でしかないので、複雑な join をしたときにはどうか分かりませんが、大量データ&単純な全検索のパターンだと、SELECT 時の恩恵はあまりないかもしれません。ええと、チューニングすれば別のような気もしますが。

■実験コードの抜粋

MySqlBulkLoader を利用

void GoBulkLoad(string CNSTR)
{
    MySqlConnection cn = new MySqlConnection(CNSTR);
    MySqlBulkLoader bl = new MySqlBulkLoader(cn);
    bl.TableName = "logs";
    bl.FieldTerminator = "t";  // タブ区切り
    bl.LineTerminator = "n";   // 改行
    bl.FileName = @"D:sitelogsout.csv";

    DateTime start = DateTime.Now;

    cn.Open();
    bl.Load();
    cn.Clone();

    DateTime end = DateTime.Now;
    label2.Text = string.Format("BulkCopy完了 {0:#.0} sec",
        ((TimeSpan)(end - start)).TotalSeconds);
}

MySqlCommand で検索

void GoSelect(string CNSTR)
{
    var ip = textBox1.Text;
    var cn = new MySqlConnection(CNSTR);
    var cmd = new MySqlCommand(
        "select count(*) from logs where ip = @ip", cn);
    cmd.Parameters.Add(new MySqlParameter("@ip", ip));
    DateTime start = DateTime.Now;
    cn.Open();
    object cnt = cmd.ExecuteScalar();
    cn.Clone();

    DateTime end = DateTime.Now;
    label4.Text = string.Format("検索結果 {0}件 {1:#.0} sec", cnt,
        ((TimeSpan)(end - start)).TotalSeconds);
}
カテゴリー: MySQL, パフォーマンス | MySQLでMySqlBulkLoaderのスピードをHDD/SSDで比較する はコメントを受け付けていません