2016年お盆にLINEっぽいリアルタイムチャットをダサい技術で再現する。

ブログを書かなきゃ、ということで

  • 映画見ながら作ったので2時間ぐらい?
  • 最速のSqliteを永続化層に使用。
  • サーバーサイドはPHP
  • リアルタイムはポーリング方式

JavaScriptのスキルここ10年ぐらい進歩していない。残念なコードができた。

貼り付けていきます。

<?php
$name = "名無し";
if(isset($_COOKIE["name"])){
    $name = $_COOKIE["name"];
}
if(isset($_REQUEST["name"])){
    $name = $_REQUEST["name"];
}
?>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,user-scalable=0">
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <!--[if lt IE 9]>
    <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <title>bandchat</title>
    <link rel="stylesheet" href="css/app.css?<?php echo time()?>" type="text/css" media="screen" />
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>    
    <script>
      var NAME = '名無し';
      function getName(){
          if($("#name").val() != ""){
             NAME = $("#name").val();
          }
          return NAME;
      }
    </script>
    <script type="text/javascript" src="js/app.js?<?php echo time()?>"></script>
    <script type="text/template" id="me">
      <div class="me arrow-right">
        <h5>{name}</h5>
        <div>{content}</div>
        <div style="text-align:right;">{date}</div>
      </div>
    </script>
    <script type="text/template" id="other">
      <div class="other arrow-left">
        <h5>{name}</h5>
        <div>{content}</div>
        <div style="text-align:right;">{date}</div>
      </div>
    </script>
  </head>
  <body>
    <div id="main">
      <h1>おれらのLINE</h1>
      <div id="chat">
      </div>
      <div id="inputarea">
      <input id="name" value="<?php echo $name?>"/><br/>
<textarea rows="3" id="input">
</textarea>
        <button id="say">送信</button>
      </div>
    </div>
  </body>
</html>
var App = {
  load : function(cursor){
      $.ajax({
                 async:false,
                 dataType: "json",
                 url: "load.php",
                 data: {cursor:cursor},
                 success:function(data){
                     if(data.length > 0){
                         App._plot(data);
                     }
                 }
             });
  },
  refresh : function(){
    App.load(App._cursor);
  },
  say : function(){
     var content = $("#input").val();
     if("" == content){
         return;
     }
     var name = getName();
     var data = {name:name,content:content};
     $.ajax({
                async:false,
                dataType: "json",
                url : "say.php",
                data : data,
                success : function(json){
                   $("#input").val("");
                }
            });
     App.refresh();
  },
  _bottom : function(){
     setTimeout(function() {
         window.scroll(0,$(document).height());
     },0);
  },
  _cursor : 0,
  _data : "",
  _plot : function(data){
      var name = getName();
      for(var i=0,l=data.length;i<l;i++){
          if(name == data[i].name) {
              App._plot_(data[i],"me");
          } else {
              App._plot_(data[i],"other");
          }
          App._cursor = data[i].id;
      }
      $("#chat").append(App._data);
      App._data = "";
      App._bottom();
  },
  _plot_ : function(chat,kind){
      if(kind == "me"){
        var template = $("#me").text();
      }else{
        var template = $("#other").text();
      }
      var plot = template.replace(/\{name\}/,chat.name).replace(/\{content\}/,chat.content).replace(/\{date\}/,chat.created_at);
      App._data += plot;
  }
};


$(function(){
  // initial load 
  App.load("load");
  // event
  $("#say").click(function(){ App.say(); });
  // polling
  setInterval(App.refresh,1000);
});
<?php

/**
 * get DB connection(PDO)
 */
function getDB(){
    $db = dirname(__FILE__) . "/chat2.db";
    $dsn = "sqlite:" . $db;
    $user = '';
    $pass = '';
    $dbh;
    try{
        $dbh = new PDO($dsn, $user, $pass);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }catch (PDOException $e){
        echo('Error:'.$e->getMessage());
        die();
    }
    return $dbh;
}
<?php
require_once(dirname(__FILE__) . "/config.php");

error_reporting(E_ALL);
setlocale(LC_ALL, "ja_JP.utf8");

function loadSay(){
    $pdo = getDB();
    if($_REQUEST["cursor"] == "load"){
        $sql = "select * from chat where content > :cursor OR 1 = 1;";
    }else{
        $sql = "select * from chat where id > :cursor ";
    }
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(":cursor" => $_REQUEST["cursor"]));
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

$arr = loadSay();

header("Content-Type:application/json");
echo json_encode($arr);
<?php
require_once(dirname(__FILE__) . "/config.php");

error_reporting(E_ALL);
setlocale(LC_ALL, "ja_JP.utf8");

$name = $_REQUEST["name"];
$content = htmlspecialchars($_REQUEST["content"]);

function replaceYoutube($string){
  $regex = "/https\:\/\/www\.youtube\.com\/watch\?v\=(.*)/";
  // https://i.ytimg.com/vi/xTU0K5q7Zbo/mqdefault.jpg
  if(preg_match($regex,$string,$m)){
      return array("replaced" => true , "content" => "<a href='". $string . "' target='_blank'>" . $string ."\n<img src='https://i.ytimg.com/vi/". $m[1] . "/mqdefault.jpg'/></a>\n"); 
  }else{
      return array("replaced" => false , "content" => $string . "\n") ;
  }
}

function replaceLink($string){
    $regex = "/http.*/";
    if(preg_match($regex,$string,$m)){
        return array("replaced" => true, "content" => "<a href='" . $string . "' target='_blank'>" . $string . "</a>\n");
    }else{
        return array("replaced" => false, "content" => $string . "\n" );
    }
}

function insertSay($name,$content){
    $sql = "insert into chat(name,content) values(:name,:content);";
    $pdo = getDB();
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(":name" => $name, ":content" => nl2br($content)));
}

$content = str_replace("\r\n","\n",$content);
$content_ar = explode("\n",$content);
for($i=0,$l=count($content_ar);$i<$l;$i++){
    $ret = replaceYoutube($content_ar[$i]);
    if($ret["replaced"]){
        $content_ar[$i] = $ret["content"];
    }else{
        $ret = replaceLink($content_ar[$i]);
        $content_ar[$i] = $ret["content"];
    }
}
$content = implode($content_ar,"");
//var_dump($content);
insertSay($name,$content);

setcookie("name",$name,time()+60*60*24*365);

header("Content-Type:application/json");
echo json_encode(array("status" => "ok"));

2016年、GlassFish V4 で teeda Seasar2 S2Daoとか動かす。動作検証 glassfish4

[環境]
GlassFish v4
teeda + S2Dao (死にプロジェクト)

本体のPKGチームに配属になったでござる。
研究開発ですねー。
2016年ですが、まだSeasar2やってます。バリバリ。
たくさんの先人たちが死んでいったタスクをさくっと解決してやりましたよ。ふむ。誇らし。
GlassFishで動かしたくて何とかしてみました。
結論を言うとif節を一つ書くだけでいけました。3行
100万行とかあるプロジェクトなのですが、これだけでいけました。

/*
 * Copyright 2004-2010 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.framework.container.autoregister;

import java.util.ArrayList;
import java.util.List;

import org.seasar.framework.util.ClassTraversal.ClassHandler;
import org.seasar.framework.util.ResourcesUtil;
import org.seasar.framework.util.ResourcesUtil.Resources;


/**
 * jarファイルに含まれているあるいはファイルシステム上(WEBINF/classesと
か)にあるコンポーネントを自動登録するためのクラスです。
 *
 * @author koichik
 */
public class ComponentAutoRegister extends AbstractComponentAutoRegister
        implements ClassHandler {

    /**
     * 参照するクラスのリストです。
     */
    protected List referenceClasses = new ArrayList();

    /**
     * デフォルトのコンストラクタです。
     */
    public ComponentAutoRegister() {
    }

    /**
     * jarファイルに含まれているクラスを追加します。jarファイルに含まれ
ているならどのクラスでもOKです。
     * このクラスを参照してjarファイルの物理的な位置を特定します。
     *
     * @param referenceClass
     */
    public void addReferenceClass(final Class referenceClass) {
        referenceClasses.add(referenceClass);
    }

    public void registerAll() {
        for (int i = 0; i < referenceClasses.size(); ++i) {
            final Class referenceClass = (Class) referenceClasses.get(i);
            final Resources resources = ResourcesUtil
                    .getResourcesType(referenceClass);
            // ここ nullガード if節.
            if(resources == null){//ここ
                System.out.println("resorcesがnullだよ");
                return;//ここ
            }//ここ
            try {
                resources.forEach(this);
            } finally {
                resources.close();
            }
        }
    }

shell芸

コメント

# ここはコメント

変数宣言

HOGE="aaa"

変数に計算結果展開とか

HOGE=$((60 * 60 * 24))
FUGA=$(find . -name *.java)

if

if [ -f test.txt ];then
    echo "test.txt存在"
fi
#「-e Liunx.txt」・・・Liunx.txtが存在しているか。
#「-f Liunx.txt」・・・Liunx.txtが存在し、通常のファイルであるか。
#「-r Liunx.txt」・・・Liunx.txtが存在し、読み取り可能であるか。
#「-s Liunx.txt」・・・Liunx.txtのファイルサイズが0でなければ真。

case

/etc/init.d/配下のスクリプトでよく使われているやつ

D=$(date "+%Y%m%d")
case "$D" in
    "20160530")
        echo "ビール"
        ;;
    "20160529")
        echo "ジュース"
        ;;
    *)
        echo "コーヒー"
        ;;
esac

for

for i in $(ls);do
    echo $i
done

FILES=`find . -name *.java -type f`
for FILE in $FILES;do
    echo $FILE
done

select

PS3="選択して:"
select SEL in apple banana grape exit;do
    case $SEL in
        apple|banana)
            echo "$SELECTEDが選択されました"
            ;;
        grape)
            echo "グレープ"
            ;;
        exit)
            echo "終了します"
            break
            ;;
        *)
            echo "1-4の番号で選んでください"
            ;;
    esac
done

関数

func(){
  echo $1 #引数一つ目
  echo $2 #引数二つ目
}
func "hoge" "fuga"

特殊変数

$0 # スクリプト名
$? # 最後の処理が成功したかどうか?
ls
if [ $? == 0 ];then
  echo "成功した"
fi

/dev/nullへ

ls > /dev/null 2>&1

とりあえず

これだけあれば戦える?

linuxでコンソールでm4a再生

自宅サーバーのubuntu適当に運用しているのでいつの間にか音声が出なくなってたりしましたが、復旧しました。適当にアップデートはあんまり良くないねー。っていうかサーバーで音楽再生できなくて良いのですがね。

GEOで980円でUSBバスパワーのスピーカーが売ってたので購入して、サーバーから再生させてみようかなーと。
昔は真空管アンプ自作して山水のビンテージもののスピーカーからとか、めちゃめちゃこだわっていましたが、2016年、おっさんになった今、980円でいいよねとなっています。人は変わるものだ。

サーバーでコンソールから再生させるのにはmocが良いです。

apt-get install moc moc-ffmpeg-plugin
$ cd Musicのあるディレクトリ
$ mocp . 

あとは使い方わからんけど矢印キーとかエンターキーとかスペースキーでなんとかなる。
hでヘルプ出るからなんとかなります。

※980円のスピーカーはやはり糞な音ですが、アラームとかにサーバーが使えるので良い感じです。

ffmpegに切れられた

macffmpegで嬉しく変換してくれるDropboxで持ってきている10年もの?ぐらいのスクリプトがあるのだけれど、今使おうとしたら

Unknown encoder 'libfaac'

ってffmpegがおこるんですよ。
ffmpeg was build without libfaac · Issue #35007 · Homebrew/legacy-homebrew · GitHub
をみてインストールし直しました。ffmpeg時間かかる。。

NASな機能を再構築した時にハマったところ

/boot以下がlinuxイメージでいっぱいになっているにもかかわらずapt-get autoremoveとかしてしまって紆余曲折あって起動しなくなりました。死に体。よくわからず復旧作業する中で外付けHDDをフォーマットしてしまい、NAS機能の再構築をするはめになりました。
フォーマット形式は今後のことも考えてexfatに選択。これが良くなかったのかもしれません。はまった。適当にフォーマットしてくれい。

fstabは

ls -l /dev/disk/by-uuid

で出てきたUUIDでマウントを考えるのですが、書き込みができないとかいろいろありました。この辺はネットに出てきている情報。
fstabは例えばこうなります。

UUID=D53D-B09F  /var/smb/sdb1   exfat   async,auto,dev,exec,gid=65534,rw,uid=65534,umask=000 0 0

で、ハマったのはこの/var/smb/sdb1にsmbdは書き込みができないこと。
なんでかなーと思っていてlsしたら

. 1970年

とか日付情報がなくなっている感じでした。どうしてくれよう。

touch .

すると日付が付き、Macからなのですが、再マウントしてやるとちゃんと書き込めるようになりました。
変な感じです。
以上。