SQLの窓

2017年06月27日


ファイルを一つアップロード : FORM の target を IFRAME にして、PHP に JavaScript を書き出させて元のページにメッセージを表示させる

昨今、ファイルをアップロードするならば、JavsScript を駆使して UI を作成し、 $.ajax で送信したほうが簡潔になりますが、ベタな FORM の記述のみで送信してそれなりのレスポンスを得ようという処理です。

デモページ


画像をクリックするとデモページへ移動します

当然ですが、デモページで実際のアップロードはできません。しかし、結果を表示するにチェックを入れて送信すると、上の画像のように実際と同じ状態で情報をシミュレートします。

UI の構築を気楽に工夫できるように、twitter-bootstrap の 4.0.0-alpha.6 を組み込んでいます

IFRAME は本来非表示で、ここに JavaScript で右上のメッセージ(toastr.js)を表示するようにしていますが、デバッグ用としても使えるようにしており、その場合は PHP 側の $_FILES の中身が見れるようになっています。

type="file" の name 属性は target で固定して php 側で使用しています。
送信データの制限 データの制限はサーバに対する無意味なアクセスを遮断するアプローチと、アプリから見たデータの制限という二つのアプローチがあります。前者がもっとも重要で、それがなければ php.ini の upload_max_filesize 内であれば結果的にそのサイズまでは送信を許してしまいます。 ※ upload_max_filesize を超えると $_FILES そのものが空になるようです。 後者は、FORM 内の name="MAX_FILE_SIZE" の値で指定できますが、完全な対策ではありません。そこで、対象ページのあるディレクトリの .htaccess 内に 『LimitRequestBody 102400』のように制限値を設定し、さらに『ErrorDocument 413』 を設定してそのページの中でエラー処理の対応をしています ▼ 413.html
<!DOCTYPE html>
<html lang="ja">
<style>
* {
	font-size: 32px;
}
</style>
<script>

	try {
		parent.$("iframe").show();
		parent.toastr.info( "ファイル のアップロードに失敗しました");
		parent.toastr.info( "エラー内容 : Request Entity Too Large" );
	}
	catch (e) {}

</script>
</head>
<body>

JavaScript の処理用に IFRAME を定義している場合で、
413 Request Entity Too Large が発生した場合にこのページが IFRAME 内に表示されます。

</body>
</html>


file_upload_html.php

$debug 変数で、実際のアップロードの可否を切り替えています。if ( !$_FILES ) { の部分は、upload_max_filesize を超えてしまった状態の対処ですが、ここでは LimitRequestBody 102400 を設定しているので発生する事はありません。
<?php
session_cache_limiter('nocache');
session_start();

header( "Content-Type: text/html; charset=utf-8" );

// *************************************
// 変数初期値
// *************************************
$debug = 0;
$upload_dir = "./upload";
if ( !$_FILES ) {
	// php.ini の upload_max_filesize を超えて、
	// Apache に制限が無いと $_FILE が空になる
	$_FILES['target']['error'] = 5;	// ユーザエラーメッセージ
}

// *************************************
// アップロード処理
// フィールド名 : target で固定
// *************************************
if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {

	// 公開状態では実行しない
	if ( $debug == 1 ) {

		$upload = realpath ( $upload_dir );
		$upload .= ( DIRECTORY_SEPARATOR . $_FILES['target']['name'] );
		if ( move_uploaded_file(
			$_FILES['target']['tmp_name'], $upload ) ) {
			$_POST['result']  = "アップロードに成功しました";
		}
		else {
			if ( !$_FILES ) {
				$_FILES['target']['error'] = 5;	// ユーザエラーメッセージ
			}
			$_POST['result']  = "アップロードに失敗しました";
		}

	}
	else {
		if ( $_FILES['target']['error'] == 0 ) {
			$_POST['result']  = "アップロードに成功しました";
		}
		else {
			$_POST['result']  = "アップロードに失敗しました";
		}
	}

}
else {
	$_POST['result']  = "POST メソッドを使用して下さい";
}

$_POST['files'] = $_FILES;

// *************************************
// この JSON 文字列を HTML 内の json
// オブジェクト定義として埋め込みます
// *************************************
$json = json_encode($_POST, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );


// *************************************
// 画面定義
// *************************************
?>
<!DOCTYPE html>
<html lang="ja">
<script>
var message = <?php require("error.json.php") ?>
var json = <?= $json ?>;

if ( json.files.target.error == 0 ) {
	parent.toastr.info(json.files.target.name + " がアップロードされました");
	parent.toastr.info("ファイルサイズ : " + json.files.target.size);
}
else {
	parent.toastr.info(json.files.target.name + " のアップロードに失敗しました");
	parent.toastr.info("エラー内容 : " + message.error[json.files.target.error]);
}

<?php if( $_POST["disp_result"] == "1" ) { ?>

	parent.$("iframe[name='upload']")
		.css("border", "1px solid #c0c0c0")
		.show();

<?php } else { ?>

	parent.$("iframe[name='upload']").hide();

<?php } ?>

</script>
</head>
<body>
<?php if( $_POST["disp_result"] == "1" ) { ?>
<textarea style='width:90%;height:350px;'><?= $json ?></textarea>

<?php } ?>
</body>
</html>


error.json.php は、PHP のエラーメッセージです。ここでは部品として埋め込んでいますが、$.ajax バージョンでも使用(error.json.php?type=json)しています。

error.json.php
<?php
if ( $_GET["type"] == "json" ) {
	session_cache_limiter('nocache');
	session_start();
	header( "Content-Type: application/json; charset=utf-8" );
}

?>
{
	"error": [
		"0 - 正常終了",
		"1 - アップロードされたファイルは、php.ini の upload_max_filesize ディレクティブの値を超えています",
		"2 - アップロードされたファイルは、HTML フォームで指定された MAX_FILE_SIZE を超えています",
		"3 - アップロードされたファイルは一部のみしかアップロードされていません",
		"4 - ファイルはアップロードされませんでした",
		"5 - 『アップロードに失敗しました』",
		"6 - テンポラリフォルダがありません。PHP 5.0.3 で導入されました",
		"7 - ディスクへの書き込みに失敗しました。PHP 5.1.0 で導入されました",
		"8 - PHP の拡張モジュールがファイルのアップロードを中止しました"
	]
}


upload.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta content="width=device-width initial-scale=1.0 minimum-scale=1.0 maximum-scale=1.0 user-scalable=no" name="viewport">
<title>HTML の FORM のみでファイルを一つアップロードする</title>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link id="link" rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>

<!-- 共通の表示 -->
<link rel="stylesheet" href="common.css">

<!-- CSS の指定 -->
<style>
/* PC 用の表示 */
@media screen and ( min-width:480px ) {
	#content {
		margin: 20px;
	}
}
/* スマホ用の表示 */
@media screen and ( max-width:479px ) {
	#content {
		margin: 0px;
	}
	body {
		width: 100%!important;
		margin: 0px;
	}
	.unit {
		width: 100%;
	}
}
</style>
<script>
// 簡易的なスマホチェックを jQuery のプロパティとして登録
jQuery.isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
if ( $.isMobile ) {
	// スマホの場合は表示画面下の中央
	toastr.options.positionClass = "toast-bottom-center";
}

$(function(){

	// このページ自身の QRコードの表示
	$('#qrcode')
		.css({ "margin" : "20px" })
		.qrcode({width: 160,height: 160,text: location.href });
		

});
</script>
</head>
<body>
<div id="head">
	<div id="title">
		<a href="./">One File アップロード</a>
	</div>
</div>

<div id="content">

	<form target="upload" enctype="multipart/form-data" action="../../php_upload/file_upload_html.php" method="POST">
		<input type="hidden" name="MAX_FILE_SIZE" value="40000">
		<input name="target" type="file" class="btn btn-info">
		<input type="submit" value="ファイルを送信" class="btn btn-success ">

		<br>
		<label for="disp_result">結果を表示する</label> <input id="disp_result" type="checkbox" name="disp_result" value="1" class="mt-5">
	</form>

</div>

<div id="qrcode"></div>

<iframe src="about:blank" name="upload" style="display:none;border:0px solid #c0c0c0;margin-left:20px;width:90%;height:400px;"></iframe>



</body>
</html>


HTML 側のダウンロード



PHP 側のダウンロード





posted by lightbox at 2017-06-27 12:55 | Comment(0) | PHP + 通信 | このブログの読者になる | 更新情報をチェックする

2017年06月20日


Android での保存用テキストデータの扱いを okio で簡素化する

OkHttp はとても簡単にインターネットへのアクセスを提供してくれます。その Okhttp が依存しているストリームの処理が okio です。

当然 okio は一般的なテキストファイルのアクセスにも簡単に使えて便利なので、Android の内部で扱われる『位置付け』別に処理を実際に使用して整理してみました。

★ 全体のソースコード
build.gradle への記述 ▼ 2017/06/20 時点 compile 'com.squareup.okio:okio:1.13.0' Runtime Permission の対応 ▼ 以下のテンプレートを使用しています Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート okio を使用した2つのメソッドを用意 okio には、BufferedSource と BufferedSink というクラスがあり、それぞれ 入力と出力を担当しています。一応サンプルとして OkioTest.java を読むと雰囲気がつかめると思いますが、欲しい機能はそれほど複雑では無いので以下の2つのメソッドだけでも十分使いこなせると思います。
	// *************************
	// テキストファイル一括読込
	// "UTF-8" or "windows-31j"
	// *************************
	private String readTextAll(InputStream inputStream, String charset) throws Exception {
		String result;

		BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));
		result = bufferedSource.readString(Charset.forName(charset));

		bufferedSource.close();
		inputStream.close();

		return result;
	}

	// *************************
	// ファイルコピー
	// *************************
	private void copy(InputStream inputStream, OutputStream outputStream) throws Exception {

		Source source = Okio.source(inputStream);
		BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
		bufferedSink.writeAll(source);

		bufferedSink.close();
		source.close();

	}

assets に syain.csv ファイルを置いて他の場所にコピー(書込み)



syain.csv は、Windows の一般的な SHIFT_JIS のテキストファイル(CSV) です。このデータを パッケージの領域と 外部ストレージにコピーして、後でテキストファイルの読み込みをテストします。

 



他にバリエーションとして、okio で直接パッケージの領域にテキストファイル(test.txt)を "UTF-8" として書き込んでいます(23〜30)。
1) UTF-8 を指定して書き込み

Charset charset = Charset.forName("UTF-8");
sink.writeString("ストレージ情報\n",charset);

2) 専用メソッドで UTF-8 として書き込み

sink.writeUtf8(Manifest.permission.CAMERA + "\n");

※ sink は BufferedSink です
さらに、プリファレンスの書き込みも行います(36〜46)。
			try {

				InputStream inputStream;
				AssetManager assetManager = MainActivity.this.getResources().getAssets();

				// ****************************************
				// syain.csv を assets から プライベートエリアへコピー
				// ****************************************
				inputStream = assetManager.open("syain.csv");
				copy(inputStream, openFileOutput("syain.csv", MODE_PRIVATE));

				// ****************************************
				// syain.csv を assets から 外部ストレージへコピー
				// ****************************************
				createDir();
				inputStream = assetManager.open("syain.csv");
				String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
				copy(inputStream, new FileOutputStream(csvPath));

				// ****************************************
				// プライベートなテキストファイルの作成と書き込み
				// ****************************************
				BufferedSink sink = Okio.buffer(Okio.sink(openFileOutput("test.txt", MODE_PRIVATE)));
				Charset charset = Charset.forName("UTF-8");
				sink.writeString("ストレージ情報\n",charset);
				sink.writeUtf8(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
				sink.writeUtf8(Manifest.permission.CAMERA + "\n");
				sink.writeUtf8(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
				sink.writeUtf8(PackageManager.PERMISSION_GRANTED + "\n");
				sink.close();

				// ****************************************
				// プライベートな小さな情報を保存する為の本来の方法
				// private_data.xml の作成
				// ****************************************
				SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
				SharedPreferences.Editor editor = data.edit();

				// 現在の年月日・時分秒を取得する為の準備
				Calendar cal = Calendar.getInstance();
				SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

				// データの書き込み
				editor.putString("now", sf.format(cal.getTime()));
				editor.putString("japan", "日本語");
				editor.commit();

			} catch (Exception e) {
				e.printStackTrace();
			}

外部ストレージにフォルダの作成

Runtime Permission の対象処理です

	// *************************
	// フォルダ作成
	// *************************
	private void createDir() {
		String imageDir = Environment.getExternalStorageDirectory().getPath() + "/textfiletest";
		Log.i("lightbox", imageDir );
		File file = new File(imageDir);
		// ディレクトリ初期作成
		if (!file.exists()) {
			if (file.mkdir() == false) {
				Log.i("lightbox", "ディレクトリを作成できませんでした");
				return;
			}
		}
	}

画面にファイルを読込み(1)

まずは、パッケージ内の領域から読み込んで画面に表示します( openFileInput )

※ 画面定義


			StringBuilder sb = new StringBuilder();

			InputStream inputStream;
			String text;

			Log.i("lightbox", "読み込み1");
			try {
				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("test.txt");
				text = readTextAll(inputStream,"UTF-8");

				sb.append(text);

				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("syain.csv");
				text = readTextAll(inputStream,"windows-31j");

				sb.append(text);

				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
				editText.setText(sb.toString());
				TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
				textView.setText(sb.toString());

				// ****************************************
				// private_data.xml からデータの取得
				// ****************************************
				SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
				Log.i("lightbox", data.getString("now", "このデータはありません"));
				Log.i("lightbox", data.getString("japan", "このデータはありません"));

			} catch (Exception e) {
				e.printStackTrace();
			}
UTF-8 と SHIFT_JIS で保存してあるファイルの一括読み込みです。キャラクタセットに関する Oracle のドキュメントはこちらになります。

※ プリファレンスは Log.i で表示しています

画面にファイルを読込み(2)

外部ストレージより読み込みます( Runtime Permision の対象処理 )
new FileInputStream(path)

※ test.txt は、画面表示の都合で読み込んでいます
			StringBuilder sb = new StringBuilder();

			InputStream inputStream;
			BufferedSource bufferedSource;
			String text;

			Log.i("lightbox", "読み込み2");
			try {
				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("test.txt");
				text = readTextAll(inputStream,"UTF-8");

				sb.append(text);

				// ****************************************
				// 外部ストレージからテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
				inputStream = new FileInputStream(csvPath);
				text = readTextAll(inputStream,"windows-31j");

				sb.append(text);

				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
				editText.setText(sb.toString());
				TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
				textView.setText(sb.toString());


			} catch (Exception e) {
				e.printStackTrace();
			}


関連する記事 : 一般的な Java のテキスト読込み

Java : テキストファイルを読み込んで正規表現で置換


posted by lightbox at 2017-06-20 21:17 | Comment(0) | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート

Runtime Permission の対応はしないとアプリが動作しないのでせざるを得ないですが、本来の処理とは無関係なのでかなり面倒な上にソースコードそのものが第三者への説明の邪魔になります。

そこで、ほぼ定型化して処理する為のテンプレートとして MainActivity への配置例と、そのために必要なクラスを用意しました。
手順1 ▼ Runtime Permission の処理は、以下から permission_170610.zip をダウンロードして下さい。 ※ CheckMyPermission の内容 手順2 ▼ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい ▼ エクスプローラから見た状態 手順3 使用したい Permission を AndroidManifest.xml に記述します
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lightbox.runtimepermissiontest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

手順4

MainActivity を以下のように記述して下さい
package com.example.lightbox.runtimepermissiontest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

	private CheckMyPermission checkMyPermission;

	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		// 全ての必要なパーミッションが許可された場合
		if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
			// onCreate で初期処理できるように、MainActivity をリスタート
			Intent intent = MainActivity.this.getIntent();
			MainActivity.this.finish();
			MainActivity.this.startActivity(intent);
		}
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// 必要なパーミッションのリスト
		ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
		// AndroidManifest.xml に記述した Permission の一覧
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
		myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,101));
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_CALENDAR,102));
		// Runtime Permission 用のクラスのインスタンス
		checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
		if ( checkMyPermission.checkPermission() ) {
			// ここから通常の初期処理
			initSettings();
		}
	}

	private void initSettings() {

	}

}


手順5

実行します。すると、初回は許可ダイアログが指定した Permission と同数表示されて、onRequestPermissionsResult が実行されます。すると、Activity が再起動され、checkMyPermission.checkPermission() が true になって initSettings() を実行する事になります。

2回目以降(全て許可した場合)は onCreate で initSettings() が常に実行される事になります。(許可ダイアログを再度表示したい場合は、アプリを削除して再度実行します)

許可ダイログ

  


関連する記事

『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』

Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )


posted by lightbox at 2017-06-20 19:35 | Comment(0) | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

2017年06月10日


WindowBuilder(Swing) で、WEBカメラを使用して画像を保存して okhttp で WEBサーバへアップロードする



必要なライブラリ

アップロードには okhttp を使用します
( ライブラリとしては okio も必要です )

JFrame を新しく作成して WEBカメラの映像をリアルタイムに表示しています。ライブラリは、webcam-capture を使用していますが、他のライブラリとして bridj-0.6.2.jar と slf4j-api-1.7.2.jar が同梱されていましたが、slf4j はもっと新しいバージョンがあり、いずれにしても slf4j-nop-1.7.25.jar が必要だったので、現時点で最新版の slf4j-api-1.7.25.jar をダウンロードして使用しました。


( Failed to load class org.slf4j.impl.StaticLoggerBinder が出る場合 )

単純な環境ならば、Maven が簡単です。(一つの IP で多くのアクセスがある場合は問題が出ます)

配布元のサンプルコード

How to display image from webcam in Swing panel (basic)

JFrame にロードする場合は、自動的に open されるようです。

WindowBuildewr のソースコード

プロジェクトの作成は、通常プロジェクトに WindowBuilder の JFrame を作成したものです。WindowBuilder の導入は『Pleiades All in One(NEON) で、Windows アプリを作成する手順( WindowBuilder + Swing デザイナー or SWT デザイナー[JFace] )』を参照して下さい
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class Main extends JFrame {

	private JPanel contentPane;
	private Webcam webcam;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Main frame = new Main();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public Main() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 559, 444);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		JButton btnNewButton = new JButton("New button");
		btnNewButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				File targetFile;

				// get image
				BufferedImage image = webcam.getImage();
				// save image to PNG file
				targetFile = new File("c:\\temp\\test.png");
				try {
					ImageIO.write(image, "PNG", targetFile);
				} catch (IOException e1) {
					// TODO 自動生成された catch ブロック
					e1.printStackTrace();
				}


				OkHttpClient client = new OkHttpClient();

				MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder();
				multipartBodyBuilder.setType(MultipartBody.FORM);
				multipartBodyBuilder.addFormDataPart(
					"target",
					targetFile.getName(),
					RequestBody.create(MediaType.parse("image/png"),targetFile)
				);
				RequestBody requestBody = multipartBodyBuilder.build();

				// 送信用のデータを作成
				Request.Builder requestBuilder = new Request.Builder();
				String url = "https://ドメイン/パス/file_upload.php";
				requestBuilder.url(url);
				requestBuilder.post(requestBody);
				Request request = requestBuilder.build();

				// 受信用のオブジェクトの準備
				Call call = client.newCall(request);
				String result = "";

				// 送信と受信
				try {

					Response response = call.execute();
					result = response.body().string();

				} catch (Exception ex) {
					ex.printStackTrace();
				}

				System.out.println(result);

			}
		});
		btnNewButton.setBounds(12, 10, 135, 21);
		contentPane.add(btnNewButton);

		loadWebCam();
	}

	private void loadWebCam(){

		webcam = Webcam.getDefault();
		webcam.setViewSize(WebcamResolution.VGA.getSize());

		WebcamPanel panel = new WebcamPanel(webcam);
		panel.setFPSDisplayed(true);
		panel.setDisplayDebugInfo(true);
		panel.setImageSizeDisplayed(true);
		panel.setMirrored(true);

		JFrame window = new JFrame("Webcam Panel");
		window.getContentPane().add(panel);
		window.setResizable(true);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.pack();
		window.setVisible(true);


	}
}


WEBサーバの PHP

アップロードの name は target で固定です。
<?php
session_cache_limiter('nocache');
session_start();

header( "Content-Type: application/json; charset=utf-8" );


if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {
 
	$upload = realpath ( '.' );
	$upload .= ( DIRECTORY_SEPARATOR . $_FILES['target']['name'] );
	if ( move_uploaded_file(
		$_FILES['target']['tmp_name'], $upload ) ) {
		$_POST['result']  = "アップロードに成功しました";
	}
	else {
		$_POST['result']  = "アップロードに失敗しました";
	}

}
else {
	$_POST['result']  = "POST メソッドを使用して下さい";
}

$_POST['files'] = $_FILES;

print json_encode($_POST, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
?>


関連する記事

PHP : MastodonOAuthPHP の HttpRequest.php の http_request を public に書き換えて、ファイルアップロード


メインの JFrame 内の JPanel に表示する



	private void loadWebCam(){

		webcam = Webcam.getDefault();
		webcam.setViewSize(WebcamResolution.VGA.getSize());

		WebcamPanel panel = new WebcamPanel(webcam);
		panel.setFPSDisplayed(true);
		panel.setDisplayDebugInfo(true);
		panel.setImageSizeDisplayed(true);
		panel.setMirrored(true);

		panelInFrame.add(panel);

	}

IP カメラを使用する場合

ライブラリのダウンロード(webcam-capture-driver-ipcam)

IP カメラは、WEBカメラを Gmax M-JPEG version を使用しています( exe ファイルを VirusTotal でチェックししています )
	// ***************************
	// JFrame に IP Cam
	// ***************************
	private void loadIpWebCam(){

		Webcam.setDriver(new IpCamDriver());

		try {
			IpCamDeviceRegistry.register(new IpCamDevice("lightbox", "http://192.168.1.4:8088/", IpCamMode.PUSH));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}

		JFrame window = new JFrame("Webcam Panel");
		window.getContentPane().add(new WebcamPanel(Webcam.getDefault()));
		window.setResizable(true);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.pack();
		window.setVisible(true);
		
		webcam = Webcam.getDefault();

	}

	// ***************************
	// JFrame 内の JPanel に IP Cam
	// ***************************
	private void loadIpWebCam2(){

		Webcam.setDriver(new IpCamDriver());

		try {
			IpCamDeviceRegistry.register(new IpCamDevice("lightbox", "http://192.168.1.4:8088/", IpCamMode.PUSH));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}

		panelInFrame.add(new WebcamPanel(Webcam.getDefault()));

		webcam = Webcam.getDefault();
	}



posted by lightbox at 2017-06-10 23:40 | Comment(0) | java : WindowBuilder | このブログの読者になる | 更新情報をチェックする

Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )

Runtime Permission の処理は、『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』でテストしたものをダウンロードできます。


※ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい


MainActivity

画面の表示はこちらで行います。onCreate で Runtime Permission のチェックの準備を行って、onRequestPermissionsResult での受け取り処理もここで完結します。

MainActivity を継承したサブクラスの onCreate でチェックを行い、既に許可済みであれば通常の初期処理を行います。

ここでは、カメラの呼び出しを行うので、カメラの Permission は必要ありませんが、ExternalStorage に画像ファイルを作成するので、AndroidManifest.xml への記述と Runtime Permission の許可処理が必要です。

また、カメラ呼び出しで private 変数がクリアされる事を想定して(そういう仕様である上に、カメラが保存したパスを返さない事から保存せざるを得ない)、MainActivity では onSaveInstanceState と onRestoreInstanceState で変数の保存と復帰を行っています。

package com.example.lightbox.cameracalltest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private CheckMyPermission checkMyPermission;

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imageUri) {
        this.imagePath = imageUri;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);

    }

    // 全ての必要なパーミッションが既に許可されているかどうか
     protected boolean  checkMyPermission() {
        return checkMyPermission.checkPermission();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString("image_uri", imagePath);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        imagePath = savedInstanceState.getString("image_uri");
    }
}


CameraCallActivity
package com.example.lightbox.cameracalltest;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CameraCallActivity extends MainActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 画面表示は super 側
        Log.i("lightbox", "CameraCallActivity : onCreate");

        if ( CameraCallActivity.this.checkMyPermission() ) {
            // 通常処理
            initSettings();
        }
    }

    private void initSettings() {

        // ボタンイベントの登録
        Button button = (Button) CameraCallActivity.this.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリックされました");

                callCamera();
            }
        });

    }

    // カメラの呼び出し
    private void callCamera() {

        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);

        String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
        File file = new File(imageDir);
        // ディレクトリ初期作成
        if (!file.exists()) {
            if (file.mkdir() == false) {
                Log.i("lightbox", "ディレクトリを作成できませんでした");
                return;
            }
        }

        // ギャラリー用画像保存パス
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";
        Log.i("lightbox",  "imagePath : " + imagePath );
        // 画像ファイルの パス を保存
        CameraCallActivity.this.setImagePath(imagePath);
        file = new File( imagePath );

        // 保存してほしい URI を引き渡す
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));

        CameraCallActivity.this.startActivityForResult(intent, 101);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if ( requestCode == 101 ) {
            if ( resultCode == Activity.RESULT_OK ) {

                // 画像表示
                Bitmap bitmap = BitmapFactory.decodeFile(CameraCallActivity.this.getImagePath());
                ImageView imageView = (ImageView) CameraCallActivity.this.findViewById(R.id.imageView);
                imageView.setImageBitmap(bitmap);

                // ギャラリーに反映
                MediaScannerConnection.scanFile(
                        CameraCallActivity.this,
                        new String[] { CameraCallActivity.this.getImagePath() },
                        new String[] { "image/jpeg" },
                        null);
            }
        }
    }
}


AndroidManifest.xml

実行するアクティビティは、CameraCallActivity なので、修正が必要です( 12行目 )
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameracalltest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".CameraCallActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameracalltest.MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher" />
</LinearLayout>




posted by lightbox at 2017-06-10 15:08 | Comment(0) | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

JSON 配列を WEB より読み出して jQuery のプラグイン『DYNATABLE』で動的にテーブルを作成して表示する


デモページ

配布サイト

DYNATABLE プラグインは、ドキュメント上では動的にテーブルを再ビルドするようには考えられていません。固定の JSON データをブラウザ側のみで処理する事を想定されています。そのサンプルは単純で、JSON データを即 TABLE 要素にセットしてくれる jQuery プラグイン『DYNATABLE』の利用 にあります。

しかしよくよく調べてみると、Stack Overflow で『動的に書き換える』方法が解答として紹介されています。その方法をもう少し進めて、整理したサンプルを作成しました。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Dynatable/0.3.1/jquery.dynatable.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Dynatable/0.3.1/jquery.dynatable.min.js"></script>
<style>
body {
	padding: 10px 0 0 20px;
}
@media screen and ( max-width:479px )
{
	body {
		padding: 0px;
	}
}
#table_wrap {
	width:900px;

}
</style>
<script>
$(function(){

	// 最初にデータなしで作成してタイトルの CSS 有効にする
	window.dynatable = $('#syain').dynatable().data( "dynatable" );

	// データのみのクリア
	$("#btn2").on( "click", function(){
		
		dynatable.settings.dataset.originalRecords = [];
		dynatable.process();

	});

	$("#btn1").on( "click", function(){
	
		console.log("クリックされました");
		$.ajax({
			url: "https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php",
			cache: false,
			data: { "name" : $("#cond").val() }
		})
		.done(function( data, textStatus ){
			console.log( "status:" + textStatus );
			console.log( "data:" + JSON.stringify(data, null, "    ") );

			// この部分で新しいデータが反映されます
			dynatable.settings.dataset.originalRecords = data;
			dynatable.process();
			
		})
		// 失敗
		.fail(function(jqXHR, textStatus, errorThrown ){
			console.log( "status:" + textStatus );
			console.log( "errorThrown:" + errorThrown );
		})
		// 常に実行
		.always(function() {
			console.log("常に実行");
		})
		;
	
	});
});
</script>
<h3>社員一覧</h3>

<input id="cond" type="text">
<input id="btn1" type="button" value="検索">
<input id="btn2" type="button" value="リセット">

<!-- dynatable ではタイトル部分のみ作成しておく -->
<div id="table_wrap" class="table-responsive">
<table id="syain" class="table table-bordered">
	<thead>
		<th>社員コード</th>
		<th>氏名</th>
		<th>フリガナ</th>
		<th>所属</th>
		<th>性別</th>
		<th>給与</th>
		<th>手当</th>
		<th>管理者</th>
	</thead>
	<tbody></tbody>
</table>
</div>




posted by lightbox at 2017-06-10 01:21 | Comment(0) | プラグイン:jQuery | このブログの読者になる | 更新情報をチェックする

2017年06月09日


JSON 配列を WEB より読み出して jQuery で動的にテーブルを作成して表示する


デモページ

関連する記事デモで実際に使用している自家製 API です

PHP : SQLインジェクション対策付きの、MySQL のデータを JSON で返す自家製 API テスト用のテンプレート3パターン

https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php
https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php?pretty=no : 整形なし
https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php?escape=no : \uXXXX にエスケープ

▼ TABLE 作成部分はほぼ同じです

FileReader で、ローカルの CSV を読み込んで(shift_jis)、jQuery でテーブルを作成して表示する

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<style>
body {
	padding: 10px 0 0 20px;
}
@media screen and ( max-width:479px )
{
	body {
		padding: 0px;
	}
}
#table_wrap {
	width:900px;

}
#syain  td {
	padding: 5px;
	border: solid #000000 1px;
	white-space: nowrap;
}
#syain  th {
	background-color: #e0e0e0;
	font-weight: bold;
	padding: 5px;
	border: solid #000000 1px;
	white-space: nowrap;
}
</style>
<script>
$(function(){

	$("#btn").on( "click", function(){
	
		console.log("クリックされました");
		$.ajax({
			url: "https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php",
			cache: false,
			data: { "name" : $("#cond").val() }
		})
		.done(function( data, textStatus ){
			console.log( "status:" + textStatus );
			console.log( "data:" + JSON.stringify(data, null, "    ") );

			// テーブル作成処理
			loadData( data );
			
		})
		// 失敗
		.fail(function(jqXHR, textStatus, errorThrown ){
			console.log( "status:" + textStatus );
			console.log( "errorThrown:" + errorThrown );
		})
		// 常に実行
		.always(function() {
			console.log("常に実行");
		})
		;		
	
	});

});


function loadData( data ) {

	console.log("処理開始");

	// テーブル表示リセット
	$("#syain tr,#syain th").remove();

	// タイトル専用
	var title_row;
	var title_col_data;
	// データ用
	var row_data;
	var col_data;

	// 行のループ  ( <tr></tr> )
	$.each(data, function( index, value ) {
	
		// 初回はタイトル作成
		if ( index == 0 ) {
			$.each( value, function( key, val ) {
				title_col_data = $("<th></th>").appendTo( "#syain" );
				title_col_data.text( key );
			});
		}

		// テーブルに行を追加	
		row_data = $("<tr></tr>").appendTo( "#syain" );
	
		// 列のループ ( <td></td> )
		$.each( value, function( key, val ) {
		
			// 行に列を追加
			col_data = $("<td></td>").appendTo( row_data );
			col_data.text( val );

		});
	
	});

}
</script>
<h3>社員一覧</h3>

<input id="cond" type="text">
<input id="btn" type="button" value="検索">

<div class="table-responsive">
<table id="syain"></table>
</div>



twitter-bootstrap は、スマホでテーブルが横スクロールする効果で使用しています( class="table-responsive" )。


posted by lightbox at 2017-06-09 22:04 | Comment(0) | jQuery | このブログの読者になる | 更新情報をチェックする

PHP : SQLインジェクション対策付きの、MySQL のデータを JSON で返す自家製 API テスト用のテンプレート3パターン

特記事項

1) ブラウザの JavaScript からドメイン違いでも呼び出せるように、Access-Control-Allow-Origin を使用
2) pretty=no で、整形しません。escape=no で、日本語はそのまま返します(デバッグ優先)

real_escape_string で SQLインジェクション対策

SQL の実行に query メソッドを使用します。その為、SQL は単純に文字列で作成するので、real_escape_string で条件内の値を $_GET より $get に変換して使用します。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// SQL インジェクション用
	foreach( $_GET as $key => $value ) {
		// $_GET を $get で代替
		$get[$key] = $mysqli->real_escape_string( $value );
	}
	// json encode オプション
	if ( $get["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $get["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like '%{$get["name"]}%'";
	}

	$result = $mysqli->query($query);
	if ( $result === false ) {
		// SQL 失敗
		$json = array( "error" => "$mysqli->error : {$query}" );
	}
	else {
		// DB 読み出し成功
		$json = $result->fetch_all( MYSQLI_ASSOC );
	}
}

print json_encode( $json, $flg );

?>


bind_param で SQLインジェクション対策

prepare メソッドを使用すると、bind_param メソッドを使用して入力値から SQL を作成する事ができます。そして、結果として get_result メソッド を使用する事によって mysqli_result を取得できるので、その後は パターン(1) と同じになります
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// MySQL ネイティブドライバ限定(PHP 5.3.0 以降)
			$result = $stmt->get_result();
			if ( $result === false ) {
				$json = array( "error3" => "$mysqli->error" );
			}
			else {
				// DB 読み出し成功
				$json = $result->fetch_all( MYSQLI_ASSOC );
			}
		}
	}
}

print json_encode( $json, $flg );

?>


get_result メソッド を使え無い場合の方法

get_result メソッドは、MySQL ネイティブドライバ限定(PHP 5.3.0 以降)なので、代替方法です。$stmt->store_result() => $stmt->bind_result => $stmt->fetch という流れになります。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// get_result() を使わない方法
			$stmt->store_result();
			$row_data = new StdClass;
			$stmt->bind_result(
				$row_data->{"社員コード"},
				$row_data->{"氏名"},
				$row_data->{"フリガナ"},
				$row_data->{"所属"},
				$row_data->{"性別"},
				$row_data->{"作成日"},
				$row_data->{"更新日"},
				$row_data->{"給与"},
				$row_data->{"手当"},
				$row_data->{"管理者"}
			);
			$json = array();
			while ($stmt->fetch()) {
				$json[] = $row_data;
			}
		}
	}
}

print json_encode( $json, $flg );

?>




posted by lightbox at 2017-06-09 20:43 | Comment(0) | PHP + データベース | このブログの読者になる | 更新情報をチェックする

2017年06月06日


Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )

商品作る場合では無く、テストで面倒なデバイス側での権限の付与の実装を避けたい場合のクラスを作ってみました。getRequestCount() のチェックをしなければ、権限を付与するまでループするようにする事もできます(許可しない場合の UI テスト等にも使えるかもしれません)

CheckMyPermission クラス
package com.example.lightbox.cameratest;

import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;

import java.util.ArrayList;

public class CheckMyPermission {

    private ArrayList<MyPermission> myPermission_list;
    private MainActivity context;

    public CheckMyPermission(MainActivity context, ArrayList<MyPermission> myPermission_list) {
        this.myPermission_list = myPermission_list;
        this.context = context;
    }

    public boolean checkPermission( int requestCode, int[] grantResults ) {
        boolean result = false;
        for( MyPermission myPermission :  myPermission_list) {
            if ( myPermission.getPermissionId() == requestCode ) {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        myPermission.setFlg(true);
                }
            }
        }
        int resultCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if ( !myPermission.getFlg() ) {
                if ( myPermission.getRequestCount() > 0 ) {
                    break;
                }
                ActivityCompat.requestPermissions(context,
                        new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                myPermission.setRequestCount(myPermission.getRequestCount()+1);
                break;
            }
            else {
                resultCounter++;
            }
        }
        if ( resultCounter == myPermission_list.size() ) {
            result = true;
        }

        return result;

    }

    public boolean checkPermission(){
        boolean result = false;
        int initCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if (ActivityCompat.checkSelfPermission(context, myPermission.getPermissionType())== PackageManager.PERMISSION_GRANTED){
                initCounter++;
                myPermission.setFlg(true);
            }
        }
        if ( initCounter == myPermission_list.size() ) {
            result = true;
        }
        else {
            for( MyPermission myPermission :  myPermission_list) {

                if ( !myPermission.getFlg() ) {
                    // 最初に許可されていないパーミッション用のダイアログを表示する
                    ActivityCompat.requestPermissions(context,
                            new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                    myPermission.setRequestCount(myPermission.getRequestCount()+1);

                    // 一つだけ処理する
                    break;
                }

            }
        }

        return result;
    }
}

対象となる Permission を ArrayList にセットとして内部で殆どの処理を実行してもらいます。ArrayList にセットする MyPermission クラスは以下のような情報を保持しています

MyPermission クラス
package com.example.lightbox.cameratest;

public class MyPermission {

    private String permissionType;
    private int permissionId;
    private boolean flg = false;
    private int requestCount = 0;

    public MyPermission(String type, int id) {
        permissionType = type;
        permissionId = id;
    }

    public String getPermissionType() {
        return permissionType;
    }

    public int getPermissionId() {
        return permissionId;
    }

    public boolean getFlg() {
        return flg;
    }

    public void setFlg(boolean flg) {
        this.flg = flg;
    }

    public int getRequestCount() {
        return requestCount;
    }

    public void setRequestCount(int requestCount) {
        this.requestCount = requestCount;
    }
}


主な処理はカメラと撮影した画像の保存ですが、カメラはテストを目的としているので 『非推奨』の 旧API を使用しています。

MainActivity
package com.example.lightbox.cameratest;

import android.Manifest;
import android.content.Intent;
import android.hardware.Camera;
import android.media.MediaScannerConnection;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private OldCamera camera;
    private CheckMyPermission checkMyPermission;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,100));
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,101));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
        // 全ての必要なパーミッションが既に許可されていた場合
        if ( checkMyPermission.checkPermission() ) {
            cameraSettings();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    private void cameraSettings() {

        camera = new OldCamera(MainActivity.this.findViewById(R.id.surfaceView) );

        // *************************************
        // ギャラリーに保存する処理
        // *************************************
        Button galleryButton = (Button) MainActivity.this.findViewById(R.id.button);
        galleryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリック");

                // 撮影
                camera.getCamera().takePicture(null, null, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {

                        if (data != null) {

                            // ギャラリー用に内部ストレージにフォルダを作成
                            String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
                            File file = new File(imageDir);
                            // ディレクトリ初期作成
                            if (!file.exists()) {
                                if (file.mkdir() == false) {
                                    Log.i("lightbox", "ディレクトリを作成できませんでした");
                                    return;
                                }
                            }

                            // ギャラリー用画像保存パス
                            Calendar cal = Calendar.getInstance();
                            SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
                            imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";

                            FileOutputStream jpg;
                            try {
                                jpg = new FileOutputStream(imagePath);
                                jpg.write(data);
                                jpg.close();

                                // ギャラリーに反映
                                MediaScannerConnection.scanFile(
                                        MainActivity.this,
                                        new String[] { imagePath },
                                        new String[] { "image/jpeg" },
                                        null);

                            }
                            catch (Exception e) {
                                e.printStackTrace();
                            }

                            // カメラ機能再開
                            camera.startPreview();

                        }
                    }
                });

            }
        });
    }

}


OldCamera クラス
package com.example.lightbox.cameratest;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import java.io.IOException;
import java.util.List;

public class OldCamera implements SurfaceHolder.Callback {

    // 古いカメラAPI。カメラで何かしたいわけでは無いのでこれで実装
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private List<Camera.Size> sizeList;

    public OldCamera(View view) {

        Log.i("lightbox", "コンストラクタ");

        // SurfaceView から holder を取り出す
        surfaceHolder = ((SurfaceView)view).getHolder();
        // この中でイベント処理を行う
        surfaceHolder.addCallback(OldCamera.this);

    }

    public Camera getCamera() {

        return camera;

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        Log.i("lightbox", "開く");

        // カメラを開く
        camera = Camera.open();
        try {
            // カメラに holder を渡す
            camera.setPreviewDisplay(holder);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        // 縦固定(AndroidManifest.xml) で、portrait 撮影を前提
        camera.setDisplayOrientation(90);

        // カメラの情報
        Camera.Parameters params = camera.getParameters();
        // サポートされているサイズの一覧
        sizeList = params.getSupportedPreviewSizes();

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        // 縦固定(AndroidManifest.xml) なので一度しか通らない
        Log.i("lightbox", "開始");

        // 縦横サイズの設定
        Camera.Parameters params = camera.getParameters();
        Camera.Size optimalSize = getOptimalPreviewSize(sizeList,width,height);
        params.setPreviewSize(optimalSize.width,optimalSize.height);
        camera.setParameters(params);

        camera.startPreview();

    }

    // http://qiita.com/zaburo/items/b5d3815d3ec45b0daf4f
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

        Log.i("lightbox", "終了");

        // プレビュー終了
        if ( camera != null ) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }

    }
}


画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameratest.MainActivity">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button" />
    </LinearLayout>

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameratest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


posted by lightbox at 2017-06-06 20:56 | Comment(0) | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

Android 6.0 の Runtime Permission に対応する前に、AndroidManifest.xml に権限の記述の必要無いプライベートな書き込みで情報を収集する

Android の中でなかなかテキストファイルに書き込む機会も無いので、FileOutputStream と SharedPreferences でカメラで撮った画像を外部ストレージに書き込むのに知っておきたい情報の確認をしています。

ボタンのイベント

一般的に3種類あるボタンのイベントの作成方法で、ボタンの id によって処理を分けていく結果になる使い方です。ソースとしては自然と連続になるので、このような場合( Write と Read ) では第三者から見たら解りやすくなると思います。(MainActivity に implements View.OnClickListener が必要ですね)

※ 他の二つは、無名(匿名)のインナーでその場に書いてしまう方法と、ボタンのプロパティにメソッド名登録して、MainActivity の メソッドとしてイベントする方法です。


書き込み処理では、BufferedWriter はほぼ無駄なので使っていません。最後に改行コードつけておけば、BufferedReader で読めますし。

書き込んだ後は、Android Device Monitor で push してリアルに確認するのがいいですね。Android に騙されないで、なんか安心します。



test.txt

※ test.txt は、files フォルダのの下にあります
ストレージ情報
/storage/emulated/0
android.permission.CAMERA
android.permission.WRITE_EXTERNAL_STORAGE
0

Environment.getExternalStorageDirectory().getPath()/storage/emulated/0 が取得できています。ここに書き込みするためには Android 6.0 では Runtime Permission の処理が必要になってくるわけですが、今は場所のみを Android Device Monitor で確認しておきます。
package com.example.lightbox.textfiletest;

import android.Manifest;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.i("lightbox", "implements View.OnClickListener したら、setOnClickListener(this)");
        findViewById(R.id.button).setOnClickListener(this);
        findViewById(R.id.buttonRead).setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        // View から ID を取得して仕分け
        if (v.getId() == R.id.button ) {

            Log.i("lightbox", "AndroidManifest.xml に権限の記述の必要無いプライベートな書き込み");
            try {
                FileOutputStream fileOutputStream = openFileOutput("test.txt", MODE_PRIVATE);
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");

                // 次の段階のストレージアクセスの為の情報集め
                outputStreamWriter.write("ストレージ情報\n");
                outputStreamWriter.write(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
                // String
                outputStreamWriter.write(Manifest.permission.CAMERA + "\n");
                outputStreamWriter.write(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
                // int
                outputStreamWriter.write(PackageManager.PERMISSION_GRANTED + "\n");

                outputStreamWriter.close();
                fileOutputStream.close();

                // プライベートな小さな情報を保存する為の本来の方法
                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                SharedPreferences.Editor editor = data.edit();

                // 現在の年月日・時分秒を取得する為の準備
                Calendar cal = Calendar.getInstance();
                SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

                editor.putString("now", sf.format(cal.getTime()));
                editor.commit();

            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else if ( v.getId() == R.id.buttonRead ) {

            Log.i("lightbox", "読み込み");
            try {
                FileInputStream fileINputStream = openFileInput("test.txt");
                InputStreamReader inputStreamReader = new InputStreamReader(fileINputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                String line_buffer;
                while( null != (line_buffer = bufferedReader.readLine()) ) {
                    // 長さ 0 で Empty
                    if (line_buffer.isEmpty()) {
                        continue;
                    }

                    Log.i("lightbox",line_buffer);

                }

                inputStreamReader.close();
                fileINputStream.close();

                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                Log.i("lightbox", data.getString("now", "このデータはありません"));

            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

    }
}

Manifest.permission.CAMERA や Manifest.permission.WRITE_EXTERNAL_STORAGE は、Runtime Permission のチェックで使う定数です。これを ActivityCompat.checkSelfPermission に渡して利用可能の有無をまずチェックします。(実際は継承元の ContextCompat のメソッド)



posted by lightbox at 2017-06-06 11:23 | Comment(0) | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり

フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
BUTTONS (CSS でボタン)
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり