月曜日, 1月 30, 2017

Azure Face APIサンプル

Azureの管理ポータルでの設定は省略。
HTMLファイルとPHPファイルのサンプル。
HTMLファイルだけで実装することも可能だが、APIを利用するためのキーの文字列をソースに入れたくないので、その部分だけPHPで実装する。
いずれのファイルもhttpdのドキュメントルート下の /cognitive/ に配置される前提。
画像ファイルを一時的に置くため、httpdのドキュメントルート下に /tmp/cognitive/ というディレクトリを作成。
アップロードする画像が規定より大きい場合、canvasを使ってリサイズしたものをアップロードするようにしてある。要HTML5。


face_api.html
----- ここから -----
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Face API</title>

<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
<style>
<!--
@media all {
  .page-title {
    color: #555555;
    font-size: x-large;
    font-weight: bold;
    text-shadow: 1px 1px 1px #999999;
  }
  #result {
    font-size: large;
    font-weight: bold;
    text-shadow: 1px 1px 1px #333333;
  }
}

@media screen and (max-width: 480px) {
  #canvas {
    width: 90%;
    width: 90vw;
  }
}
-->
</style>
</head>
<body>
<div class="container">

<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/cognitive/">Cognitive Services Examples</a>
</nav>
</div>
</div>

<div class="row">
<div class="col-sm-12">
<h3 class="page-title">Face API</h3>
</div>
</div>
<br />

<div class="row">
<div class="col-sm-12">
<strong> 画像ファイルを選択し、「送信」ボタンをクリックしてください。</strong>
</div>
</div>
<br />

<div class="row">
<div class="col-sm-12">

<div class="card">
<div class="card-body">
<form id="upload-img-file-form" method="POST">

<div class="form-group row">
<label for="img-file" class="col-sm-2 col-form-label">画像ファイル</label>
<div class="col-sm-4">
<input type="file" id="img-file" name="img-file"  class="form-control btn btn-default" />
</div>
</div>

<div class="form-group row">
<div class="col-sm-10 ml-sm-auto">
<button type="button" id="submit-btn" class="btn btn-primary">送信</button>
</div>
</div>

</form>
</div>
</div>

</div>
</div>
<br />

<div class="row">
<div class="col-sm-12">
<canvas id="canvas"></canvas>
</div>
</div>
<br />

<div class="row">
<div class="col-sm-4">
<div id="result"></div>
</div>
</div>

</div>

<!-- template -->
<script type="text/template" id="result-table-template">
<table class="table table-bordered">
<tr>
<td colspan="2">性別</td><td><div class="float-sm-right"><%= gender %></div></td>
</tr>
<tr>
<td colspan="2">年齢</td><td><div class="float-sm-right"><%= age %></div></td>
</tr>
<tr>
<td colspan="2">眼鏡</td><td><div class="float-sm-right"><%= glasses %></div></td>
</tr>
<tr>
<td rowspan="8">感情</td><td>Anger (怒り)</td><td><div class="float-sm-right"><%= anger %></div></td>
</tr>
<tr>
<td>Contempt (軽蔑)</td><td><div class="float-sm-right"><%= contempt %></div></td>
</tr>
<tr>
<td>Disgust (嫌悪)</td><td><div class="float-sm-right"><%= disgust %></div></td>
</tr>
<tr>
<td>Fear (恐怖)</td><td><div class="float-sm-right"><%= fear %></div></td>
</tr>
<tr>
<td>Happiness (幸福)</td><td><div class="float-sm-right"><%= happiness %></div></td>
</tr>
<tr>
<td>Neutral (ニュートラル)</td><td><div class="float-sm-right"><%= neutral %></div></td>
</tr>
<tr>
<td>Sadness (悲しみ)</td><td><div class="float-sm-right"><%= sadness %></div></td>
</tr>
<tr>
<td>Surprise (驚き)</td><td><div class="float-sm-right"><%= surprise %></div></td>
</tr>
<tr>
<td rowspan="2">化粧</td><td>目</td><td><div class="float-sm-right"><%= eyeMakeup %></div></td>
</tr>
<tr>
<td>口</td><td><div class="float-sm-right"><%= lipMakeup %></div></td>
</tr>
</table>
</script>

<!-- javascript -->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js" integrity="sha256-G7A4JrJjJlFqP0yamznwPjAApIKPkadeHfyIwiaa9e0=" crossorigin="anonymous"></script>
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" integrity="sha256-9wRM03dUw6ABCs+AU69WbK33oktrlXamEXMvxUaF+KU=" crossorigin="anonymous"></script>
<script>
(function() {

"use strict";

const root = this;
const _    = root._;
const $    = root.jQuery;

const apiUrl = '/cognitive/face_api.php';
const resultTableTemplate
  = _.template($('#result-table-template').html());

const mathRound = function(val) {
  return (val * 100).toFixed(2);
};

const toBlob = function(base64, type) {
  const bin = atob(base64.replace(/^.*,/, ''));
  const buffer = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) {
    buffer[i] = bin.charCodeAt(i);
  }

  const blob = new Blob([buffer.buffer], {
    type: type
  });
  return blob;
};

$(function() {
  let file;
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const fd = new FormData();
  const mimeType = new Array('image/jpeg', 'image/png', 'image/gif', 'image/bmp');
  const maxFileSizeMB = 4;
  const minPix = 36;
  const maxPixels = 4096;

  $('#img-file').on('change', function(evt) {
    $('#result').html("");
    file = evt.target.files[0];
    if (mimeType.indexOf(file.type) == -1) {
      alert('ファイル形式がJPEG、PNG、GIF、BMP以外です。');
      return false;
    }
    if (file.size > maxFileSizeMB * 1024 * 1024) {
      alert('ファイルサイズが' + maxFileSizeMB + 'MBを超えています。');
      return false;
    }

    const reader = new FileReader();
    reader.onload = (function(f) {
      return function(e) {
        const imgPath = e.target.result;
        const img = new Image();
        img.src = imgPath;
        img.onload = function() {
          if (img.width < minPix || img.height < minPix) {
            alert('画像のサイズは、縦横ともに' + minPix + 'ピクセル以上必要です。');
            return false;
          }
          let scale = 1;
          if (img.width > img.height && img.width > maxPixels) {
            scale = maxPixels / img.width;
            alert('画像の最大幅が' + maxPixels + 'ピクセルを超えています。');
            return false;
          }
          else if (img.width < img.height && img.height > maxPixels) {
            scale = maxPixels / img.height;
            alert('画像の最大高が' + maxPixels + 'ピクセルを超えています。');
            return false;
          }
          canvas.width = img.width * scale;
          canvas.height = img.height * scale;
          context.drawImage(this, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
          fd.append("img-file", toBlob(canvas.toDataURL(file.type), file.type), file.name);
        };
      };
    })(file);
    reader.readAsDataURL(file);
  });

  $('#submit-btn').on('click', function() {
    $('#result').html("");
    if (!file) {
      alert('画像を選択してください。');
      return false;
    }

    $.blockUI({
      message : '<img src="/common/img/gif-load.gif" />',
      css : {
        color : '',
        border : '',
        backgroundColor : ''
      },
      overlayCSS : {
        backgroundColor: '#FFFFFF'
      }
    });

    $.ajax({
      url : apiUrl + '?api_type=detect&returnFaceAttributes=age,gender,smile,facialHair,glasses,headPose,emotion,hair,makeup,accessories',
      type : "POST",
      data : fd,
      processData : false,
      contentType : false,
      dataType : "json"
    }).done(function(data, status, xhr) {
      for (let i in data) {
        $('#result').append(
          '<span id="result' + (i + 1) + '">'
          + resultTableTemplate({
              gender : data[i].faceAttributes.gender === 'male' ? '男性' : '女性',
              age : data[i].faceAttributes.age,
              glasses : data[i].faceAttributes.glasses === 'NoGlasses' ? 'なし' : 'あり',
              anger : mathRound(data[i].faceAttributes.emotion.anger),
              contempt : mathRound(data[i].faceAttributes.emotion.contempt),
              disgust : mathRound(data[i].faceAttributes.emotion.disgust),
              fear : mathRound(data[i].faceAttributes.emotion.fear),
              happiness : mathRound(data[i].faceAttributes.emotion.happiness),
              neutral : mathRound(data[i].faceAttributes.emotion.neutral),
              sadness : mathRound(data[i].faceAttributes.emotion.sadness),
              surprise : mathRound(data[i].faceAttributes.emotion.surprise),
              eyeMakeup : data[i].faceAttributes.makeup.eyeMakeup === true ? 'あり' : 'なし',
              lipMakeup : data[i].faceAttributes.makeup.lipMakeup === true ? 'あり' : 'なし',
            })
          + '</span><br />'
        );

        context.beginPath();
        if (i % 7 === 0) {
          context.strokeStyle = 'rgb(255, 0, 0)';
          $('#result' + (i + 1)).css('color', '#FF0000');
        }
        else if (i % 7 === 1) {
          context.strokeStyle = 'rgb(0, 255, 0)';
          $('#result' + (i + 1)).css('color', '#00FF00');
        }
        else if (i % 7 === 2) {
          context.strokeStyle = 'rgb(0, 0, 255)';
          $('#result' + (i + 1)).css('color', '#0000FF');
        }
        else if (i % 7 === 3) {
          context.strokeStyle = 'rgb(0, 255, 255)';
          $('#result' + (i + 1)).css('color', '#00FFFF');
        }
        else if (i % 7 === 4) {
          context.strokeStyle = 'rgb(255, 255, 0)';
          $('#result' + (i + 1)).css('color', '#FFFF00');
        }
        else if (i % 7 === 5) {
          context.strokeStyle = 'rgb(255, 0, 255)';
          $('#result' + (i + 1)).css('color', '#FF00FF');
        }
        else if (i % 7 === 6) {
          context.strokeStyle = 'rgb(0, 0, 0)';
          $('#result' + (i + 1)).css('color', '#000000');
        }
        context.strokeRect(data[i].faceRectangle.left, data[i].faceRectangle.top, data[i].faceRectangle.width, data[i].faceRectangle.height);
      }
    }).fail(function(xhr, status, error) {
      console.log(JSON.stringify(xhr));
      console.log(status);
      console.log(error);
    }).always(function() {
      $(document).ajaxStop($.unblockUI);
    });
  });
});

}).call(this);
</script>

</body>
</html>
----- ここまで -----

face_api.php
----- ここから -----
<?php
require_once 'HTTP/Request2.php';

if ($_FILES['img-file']['tmp_name']) {
    $req_param = "";
    foreach ($_GET as $key => $value) {
        if ($key != 'api_type') {
            $req_param .= '&' . $key . '=' . $value;
        }
    }
    if ($req_param) {
        $req_param = ltrim($req_param, '&');
        $req_param = '?' . $req_param;
    }

    $request = new Http_Request2('https://westus.api.cognitive.microsoft.com/face/v1.0/' . $_GET['api_type'] . $req_param);
    $request->setAdapter('curl');

    $headers = array(
        'Content-Type' => 'application/json',
        'Ocp-Apim-Subscription-Key' => 'Azureの管理ポータルでコピーしたキー文字列',
    );

    $request->setHeader($headers);
    $request->setMethod(HTTP_Request2::METHOD_POST);

    $uploaddir = '/var/www/html/tmp/cognitive/';
    $uploadfile = $uploaddir . basename($_FILES['img-file']['name']);
    move_uploaded_file($_FILES['img-file']['tmp_name'], $uploadfile);
    $url_base = 'http://www.hpi-portal.com/tmp/cognitive/';
    $url = $url_base . basename($_FILES['img-file']['name']);
    $data = array('url' => $url);
    $request->setBody(json_encode($data));

    try {
        $response = $request->send();
        echo $response->getBody();
        if (file_exists($uploadfile)) {
            unlink($uploadfile);
        }
    }
    catch (HttpException $ex) {
        echo $ex;
        if (file_exists($uploadfile)) {
            unlink($uploadfile);
        }
    }
}
?>
----- ここまで -----

土曜日, 1月 07, 2017

初Mojolicious::Liteアプリサンプル

OS: CentOS 7.3
Perl: 5.24.0
Mojolicious: 7.14
DBI: 1.636
DBIx::Connector: 0.56
DBD::ODBC: 1.56

----- ここから -----
#!/usr/bin/env perl

use strict;
use warnings;

use Mojolicious::Lite;
use DBIx::Connector;

my $db_host   = 'db1';
my $dsn       = 'dbi:ODBC:' . $db_host;
my $db_name   = 'sample';
my $db_user   = 'db_user_name';
my $db_passwd = 'db_user_passwd';

# DB接続
my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
helper db => sub { return $conn->dbh; };

# サイトID取得
helper get_site_id => sub {
    my $self = shift;
    my $stmt = "SELECT [site_id] FROM $db_name.$db_user.site";
    my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
    $sth->{LongTruncOk}=1;
    $sth->{LongReadLen}=2000000;
    $sth->execute or die $self->db->errstr;
    my @site_ids = ();
    while (my @row = $sth->fetchrow_array) {
        push @site_ids, $row[0];
    }
    $sth->finish;
    return \@site_ids;
};

# サイトプロパティ取得
helper get_site_properties => sub {
    my $self = shift;
    $site_id = $_[0];
   
    if ($site_id) {
        my $stmt = "SELECT [property_key], [property_value], [description] FROM $db_name.$db_user.site_properties WHERE site_id = ?";
        my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
        $sth->{LongTruncOk}=1;
        $sth->{LongReadLen}=2000000;
        $sth->execute($site_id) or die $self->db->errstr;
        my @properties = ();
        while (my @row = $sth->fetchrow_array) {
            push @properties, {'property_key' => $row[0], 'property_value' => $row[1], 'description' => $row[2]};
        }
        $sth->finish;
        return \@properties;
    }
};

# ベースルートの設定
any '/' => sub {
    my $self = shift;
    my $site_ids = $self->get_site_id();
    $self->stash( site_ids => $site_ids );
    $self->render('index');
};

# サイトプロパティ取得用ルートの設定
any '/site_properties' => sub {
    my $self = shift;
    my $site_id = $self->param('site_id');
    my $properties = $self->get_site_properties($site_id);
    $self->render(json => $properties);
};

app->start;

__DATA__

@@ index.html.ep

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>e2R Utilities</title>
<%= stylesheet '//www.foobar.com/common/css/bootstrap.min.css' %>
<%= stylesheet '//www.foobar.com/common/css/bootstrap-theme.min.css' %>
</head>
<body>
<div class="container">

<div class="row">
<div class="col-md-12">
<h3>ユーティリティ</h3>
</div>
</div>
<br />

<div class="row">
<div class="col-md-12">

<div class="well well-sm">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="site_id" class="col-sm-2 control-label">サイトID</label>
<div class="col-sm-6">
<select name="site_id" class="form-control">
<option value=""></option>
% foreach my $site_id (@$site_ids) {
<option value="<%= $site_id %>"><%= $site_id %></option>
% }
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" id="site-property-btn" class="btn btn-primary">サイトプロパティ表示</button>
</div>
</div>
</form>
</div>

</div>
</div>
<br />

<div class="row">
<div class="col-md-12">
<div id="site-property-block">
<strong>サイトプロパティ</strong><br />
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>キー</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody id="site-property-list"></tbody>
</table>
</div>
</div>
</div>

</div>

<!-- template -->
<script type="text/template" id="property-list-template">
<tr>
<td>{{ property_key }}</td>
<td>{{ property_value }}</td>
<td>{{ description }}</td>
</tr>
</script>

<!-- javascript -->
%= javascript '//www.foobar.com/common/js/underscore.min.js';
%= javascript '//www.foobar.com/common/js/jquery.min.js';
%= javascript begin
(function() {

var root = this,
    _    = root._,
    $    = root.jQuery;

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g
};

$.ajaxSettings.contentType
  = 'application/x-www-form-urlencoded; charset=UTF-8';

var propertyListTemplate
  = _.template($("#property-list-template").html());

var escapeHtml = function(string) {
  if (typeof string !== 'string') {
    return string;
  }
  return string.replace(/[&'`"<>]/g, function(match) {
    return {
      '&': '&amp;',
      "'": '&#x27;',
      '`': '&#x60;',
      '"': '&quot;',
      '<': '&lt;',
      '>': '&gt;',
    }[match]
  });
};

$(function() {
  $('#site-property-block').hide();

  $('#site-property-btn').on('click', function() {
    $('#site-property-block').hide();

    if (!$('[name=site_id]').val()) {
      alert('サイトIDを選択してください。');
      return false;
    }

    $.ajax({
      url : '/app/site_properties',
      type : "POST",
      cache : false,
      dataType : "json",
      data : {
        "site_id" : $('[name=site_id]').val()
      }
    }).done(function(json) {
      var propertyListHtml = "";
      _.each(json, function(value) {
        propertyListHtml
          += propertyListTemplate({
               property_key : value["property_key"],
               property_value : escapeHtml(value["property_value"]),
               description : value["description"]
             });
      });
      $('#site-property-list').html(propertyListHtml);
      $('#site-property-block').show();
    }).fail(function() {
      alert("サイトプロパティの取得に失敗しました。");
      return false;
    });
  });
});

}).call(this);
% end

</body>
</html>
----- ここまで -----

Mojolicious::LiteのWebアプリを実行する環境設定

OSはCentOS 7.3。
アプリはStarman(PSGIサーバ)で実行する。

1. perlbrewのインストール

$ curl -kL http://install.perlbrew.pl | bash
$
$ echo "source ~/perl5/perlbrew/etc/bashrc" >> ~/.bashrc
$ source ~/.bashrc


2. 最新安定版Perlをperlbrewでインストール

$ perlbrew available
$
$ perlbrew install 5.24.0
$
$ perlbrew list
$ perlbrew switch 5.24.0
$ which perl
$ perl -v


3. cpanmのインストール

$ perlbrew install-cpanm


4. Mojoliciousのインストール

$ cpanm Mojolicious


5. DBD::ODBCとDBIx::Connectorのインストール

$ cpanm DBD::ODBC
$
$ cpanm DBIx::Connector


6. LWPのインストール

$ cpan Bundle::LWP


7. Starmanのインストール

$ cpanm Starman


8. Starman起動スクリプトサンプル

ログインIDがuser1、アプリのファイルmojo_test.plが/var/www/app/に配置されている前提。
/var/www/appにはuser1の実行・読み・書き権限があること。

----- ここから -----
#!/bin/sh

/usr/bin/su - user1 -c '/home/user1/perl5/perlbrew/perls/perl-5.24.0/bin/starman --port=3000 --daemonize --pid=/var/www/app/pid /var/www/app/mojo_test.pl'
----- ここまで -----

実行は

$ sudo starman_start.sh

のように行う。


9. Starman停止スクリプトサンプル

----- ここから -----
#!/bin/sh

/usr/bin/cat /var/www/app/pid | /usr/bin/xargs /usr/bin/kill
----- ここまで -----

実行は

$ sudo starman_stop.sh

のように行う。


10. Apacheのリバースプロキシの設定サンプル

$ sudo touch /etc/httpd/conf.d/starman.conf
$ sudo vi /etc/httpd/conf.d/starman.conf

----- ここから -----
ProxyRequests Off
ProxyPass /app http://localhost:3000
ProxyPassReverse /app http://localhost:3000
----- ここまで -----

LinuxからWindowsのSQL ServerにODBCで接続

以前にも似たような投稿をしているが、現時点で最新の環境であることと、以前とは少し設定が異なるので再度投稿。
LinuxはCentOS 7.3。

1. 各種RPMパッケージインストール

$ sudo yum install unixODBC*
$ sudo yum install freetds*
$ sudo yum install perl-DBI
$ sudo yum install perl-DBIx-Connector --enablerepo=epel
$ sudo yum install perl-DBD-ODBC


2. 設定ファイル編集、作成

$ sudo vi /etc/odbcinst.ini

末尾につぎの行を追加(odbc.iniで指定するドライバの設定)

----- ここから -----
[FreeTDS]
Description=FreeTDS ODBC
Driver=/usr/lib64/libtdsodbc.so
Setup=/usr/lib64/libtdsS.so
UsageCount=1
----- ここまで -----


$ sudo touch /etc/odbc.ini
$ sudo vi /etc/odbc.ini

つぎの行を追加(ホスト名でDSNの設定をする)

----- ここから -----
[db1]
Description=SQL Server 2012
Driver=FreeTDS
Trace=No
Server=192.168.10.1
Database=master
Port=1433
TDS_Version=8.0
ClientCharset=UTF-8
----- ここまで -----


3. 接続テスト

$ isql -v db1 db_user_name db_user_passwd


4. Perlサンプル

データベースsampleにカラム[group_id][user_id]を持つテーブルtest_tblがある前提。

----- ここから -----
#!/usr/bin/env perl

use strict;
use warnings;

use DBIx::Connector;

my $db_host   = 'db1';
my $dsn       = 'dbi:ODBC:' . $db_host;
my $db_name   = 'sample';
my $db_user   = 'db_user_name';
my $db_passwd = 'db_user_passwd';

my $test_user  = 'connect_test_user';
my $test_group = 'connect_test_group';

my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
my $dbh  = $conn->dbh;

# test_tblに対象レコードがないことの確認(何も出力されない)
my $stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
my $sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
    print "select1:@row\n";
}
$sth->finish;

# test_tblにレコード作成
$stmt = "INSERT INTO $db_name.$db_user.test_tbl ([group_id], [user_id]) VALUES (?, ?)";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
my $rc = $sth->execute($test_group, $test_user) or die $dbh->errstr;
print "insert:$rc\n";
$sth->finish;

# test_tblに対象レコードがあることの確認
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
    print "select2:@row\n";
}
$sth->finish;

# test_tblからレコード削除
$stmt = "DELETE FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$rc = $sth->execute($test_user) or die $dbh->errstr;
print "delete:$rc\n";
$sth->finish;

# test_tblに対象レコードがないことの確認(何も出力されない)
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
    print "select3:@row\n";
}
$sth->finish;

$conn->disconnect;

exit;
----- ここまで -----

Azure Bot Service + Emotion API + LUIS

Qiitaに投稿したのでそちらを参照。

http://qiita.com/perlfreak/items/ec6a420dfefdd2e4fce5

火曜日, 12月 13, 2016

特定のドメイン宛のメールキューの削除

$ mailq | tail -n '+2'  | grep -v '^ *(' | awk 'BEGIN { RS = "" } { if ( $7 == "送信元アドレス" && $8 ~ /送信先ドメイン$/ && $9 == "") print $1 }'| tr -d '*!' | sudo postsuper -d -

火曜日, 7月 19, 2016

CentOS7のDBD::Sybaseでレコードの更新に失敗する場合

insert文やdelete文を実行する前に

$dbh->do('SET ANSI_NULL_DFLT_ON, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON SET ANSI_NULL_DFLT_OFF, ARITHIGNORE, CURSOR_CLOSE_ON_COMMIT, IMPLICIT_TRANSACTIONS, NOCOUNT, XACT_ABORT , NUMERIC_ROUNDABORT OFF');

のようなクエリを実行する。
なお、確認したDBD::Sybaseのバージョンは、1.14。

月曜日, 6月 20, 2016

ファイルパスとファイルサイズでリモートホストとdiff

リモートホストにはリモートユーザでパスワードなしでssh接続できる前提。

diff <(ssh リモートユーザ@リモートホスト 'ls -l $(find 対象ディレクトリのパス -type f)' | awk '{print $5 " " $10;}') <(ls -l $(find 対象ディレクトリのパス -type f) | awk '{print $5 " " $10;}')

木曜日, 3月 24, 2016

Ubuntu 15.10にDockerをインストール

プロキシ経由でインターネットにアクセスする環境用。
CentOSのイメージを入れて起動するまで。

(参照)
https://docs.docker.com/engine/installation/linux/ubuntulinux/
https://github.com/docker/compose/releases


 $ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
 $
 $ cd /etc/apt/sources.list.d/
 $
 $ sudo touch docker.list
 $
 $ sudo vi docker.list

(つぎの行を追加)

deb https://apt.dockerproject.org/repo ubuntu-wily main

 $
 $ sudo apt-get update
 $ sudo apt-cache policy docker-engine
 $
 $ sudo apt-get install docker-engine
 $
 $ sudo gpasswd -a ${USER} docker
 $
 $ cd /etc/systemd/system
 $
 $ sudo mkdir docker.service.d
 $
 $ cd docker.service.d
 $
 $ sudo touch http-proxy.conf
 $
 $ sudo vi http-proxy.conf

(つぎの行を追加)

[Service]
Environment="HTTP_PROXY=http://172.16.10.11:3128/"

 $
 $ sudo service docker start
 $
 $ sudo bash -c 'curl -L https://github.com/docker/compose/releases/download/1.6.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose'
 $
 $ sudo chmod +x /usr/local/bin/docker-compose
 $
 $ docker run hello-world
 $
 $ docker pull centos
 $
 $ docker images
 $
 $ docker run -it centos:latest
 $

月曜日, 2月 29, 2016

Javaのpropertiesファイル文字コード変換

変換前UTF-8で編集した場合

 $ native2ascii -encoding UTF-8 hoge.properties.UTF-8 hoge.properties

水曜日, 7月 22, 2015

Microsoft Azureで仮想マシンが応答しなくなったときの対応

Azureの管理ポータル上は稼働しているように見える仮想マシン上のサービスが利用できず、リモート接続もできなくなった場合、仮想マシンの構成を変更して保存すると復旧する確率が高い。

SQLite3のテーブル定義の変更


まず、データベースのdumpをとる。

 # echo '.dump' | sqlite3 hoge.db > hoge.dump

hoge.dumpをエディタで開き、CREATE TABLEのところを適宜変更する。

 # mv hoge.db hoge.db.bak
 # cat hoge.dump | sqlite3 hoge.db

以上

水曜日, 4月 22, 2015

テキストエディタGitHub Atomのバージョンは当面0.188.0で固定がよろしいようで(Windows版) (解決)

Windowsのシステム環境変数に

変数名:ATOM_NODE_URL
変数値:http://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist

を追加することで問題解消。

---------------------------------------------------------------------------------
GitHubのAtomというテキストエディタがかなり良さ気だが、バージョン0.189.0から機能を追加する各種パッケージがインストールできなくなった。
Windows7 pro sp1 (64bit) のproxy経由。

https://github.com/atom/atom/releases

から0.188.0のZIP版をダウンロードし、解凍したものを使えば自動updateされないので、このバグが解消されるまでは当面このバージョンで。
パッケージがインストールできない状態では流石に使いにくい。

木曜日, 2月 05, 2015

MS SQL Serverで復旧モデルが「完全」の場合はトランザクションログをバックアップ

MS SQL Serverでミラーリングの構成にする場合、データベースの復旧モデルは「完全」でなくてはならない。
その場合、トランザクションログのバックアップを定期的にとらないと、ログファイルの肥大化を止められない。
データベースのバックアップをとるだけではトランザクションログのバックアップはとられないので、メンテナンスプランでデータベースのバックアップをとるのと同時に別途トランザクションログのバックアップもとるように設定する必要がある。

土曜日, 1月 10, 2015

Linux 特定のディレクトを除くディレクトリ配下のファイルの拡張子を変更する

カレントディレクトリで foo という文字列を含むディレクトリを除くディレクトリ配下のindex.htmlをindex.html.orgに変更する場合

$ find . -path "./*foo*" -prune -o -type f -name "index.html" | perl -nle '$o=$_;s/\.html$/\.html\.org/;rename $o,$_'

AzureでクラウドサービスのDNS逆引き設定

mail.hoge.comがDNSでhogemail.cloudapp.netのCNAMEで登録されている前提。
PowerShell を起動し、つぎのコマンドを実行し、一応クラウドサービスのサービス名を確認する。

PS C:\> Get-AzureService | fl ServiceName

つぎのようにコマンドを実行し、クラウドサービスのパブリック仮想 IP (VIP) アドレスの逆引き設定をする。

PS C:\> Set-AzureService -ServiceName "hogemail" -Description "Reverse DNS" -ReverseDnsFqdn "mail.hoge.com."

金曜日, 12月 12, 2014

SQL Serverでミラーリング状態の確認

Management Studioで

SELECT DB_NAME(database_id) [DBName],mirroring_role_desc,mirroring_state_desc,mirroring_witness_state_desc FROM sys.database_mirroring WHERE mirroring_guid IS NOT NULL ORDER BY DBName

を実行する。
コマンドプロンプトなら

sqlcmd -S localhost -U sa -P パスワード -d master -Q "SELECT DB_NAME(database_id) [DBName],mirroring_role_desc,mirroring_state_desc,mirroring_witness_state_desc FROM sys.database_mirroring WHERE mirroring_guid IS NOT NULL ORDER BY DBName"

を実行する。

火曜日, 11月 04, 2014

SQL Server でミラーリング削除

パートナー(ミラー先)に接続し、Management Studioで

ALTER DATABASE データベース名 SET PARTNER OFF

を実行する。

木曜日, 10月 02, 2014

AzureのCentOSにはswapがない!

Azureの仮想マシンのCentOSにはswapがない。
そこでswapを非永続ディスク上に作成する。
/etc/waagent.conf を編集する。
変更箇所は

ResourceDisk.EnableSwap=y
ResourceDisk.SwapSizeMB=5120

の2行。
/etc/fstab に

/mnt/resource/swapfile swap swap defaults 0 0

を追記する。
これでOSを再起動すると、/mnt/resource に swapfile が作成される。

火曜日, 9月 30, 2014

Tomcat8.0でシンボリックリンクを有効にする

$CATALINA_HOME/conf/context.xml の <Context> のすぐ後に

<Resources className="org.apache.catalina.webresources.StandardRoot" allowLinking="true"/>

を追記する。
バージョン8.0からこうなったみたい。