uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

自分の写真整理するためのGoスクリプトを書いた。

みなさん大掃除は済みましたか? 自分は今パソコンの中を大掃除中です。

今年たくさん写真とったおかげでMACのディスクをかなり圧迫していてちょうどそれの整理をしています。

自分のカメラは1枚の写真に対してORFとJPGファイルを2つ生成するような設定となっています。 ただ、ORFファイルがあればJPGは要らないな〜と思ったので今回まとめて整理出来るスクリプトを書きました。

ディレクトリを引数にとってそのディレクトリ以下を探索して、
JPGとORFファイルを重複して持っているものは削除するというものです。

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "regexp"
    //    "os"
    "flag"
    "log"
    "os/exec"
)

const (
    DEBUG     = false
    EXTENSION = ".JPG"
    WALK_PATH = "/Users/hogehoge/Dropbox/Pictures/写真 Library.photoslibrary/"
    TRASH_BOX = "~/.Trash"
)

const raw = `
on run argv
  tell application "Finder"
    repeat with f in argv
      move (f as POSIX file) to trash
    end repeat
  end tell
end run
`

var trash = filepath.Join(os.Getenv("HOME"), ".Trash")

func go2Trash(f string) error {
    bin, err := exec.LookPath("osascript")
    if err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    if _, err = os.Stat(trash); err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    path, err := filepath.Abs(f)
    if err != nil {
        return err
    }

    params := []string{"-e", raw, path}
    cmd := exec.Command(bin, params...)
    if err = cmd.Run(); err != nil {
        return err
    }
    return nil
}

func WalkFunc(path string, info os.FileInfo, err error) error {
    if err != nil {
        fmt.Println(err)
        return nil
    }
    if info.IsDir() {
        return nil
    }

    if ext := filepath.Ext(path); ext == EXTENSION {
        pat, err := regexp.Compile(EXTENSION + "$")
        if err != nil {
            fmt.Println(err)
            return nil
        }
        orfpath := pat.ReplaceAllString(path, ".ORF")

        if _, berr := os.Stat(orfpath); berr == nil {
            fmt.Println("ORF File Exist: ", path)
            if dryrun {
                // if dry-run mode, file isn't deleted.
                log.Println("next file will be deleted: ", path)
                return nil
            }
            // File Remove Process
            if rename {
                derr := go2Trash(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            } else {
                derr := os.Remove(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            }
        }
    }
    return nil
}

var path string
var rename, dryrun bool

func init() {
    flag.StringVar(&path, "path", WALK_PATH, "Pictures filepath.")
    flag.BoolVar(&dryrun, "n", false, "dry run flag")
    flag.BoolVar(&rename, "trash", true, "go to trashbox")
    flag.Parse()
    debugp(fmt.Sprintf("dryrun: %s", dryrun))
    debugp(fmt.Sprintf("trash: %s", rename))

    if path != WALK_PATH {
        tmp_p, err := filepath.Abs(path)
        if err != nil {
            log.Fatalln(err)
        }
        path = tmp_p
    }
    debugp(path)
}

func debugp(debugObj interface{}) {
    if DEBUG {
        fmt.Println(debugObj)
    }
}

func main() {
    f := WalkFunc
    err := filepath.Walk(path, f)
    if err != nil {
        fmt.Println(err)
    }
}

機能は以下のとおり。

  • ドライラン
  • ゴミ箱に入れずに直接削除モード

あとはこのスクリプトをビルドしてドライランで削除されるファイルを確認。 問題がなければドライランオプションを外して削除という流れです。

$ go build -o walker
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary -n
<中略>
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary
〜〜〜〜〜〜
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200882.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200883.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200886.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200889.JPG
〜〜〜〜〜〜

こんなスクリプトでもうまく動きましたが実行してみてると1つ課題が見つかりました。 コマンド実行でゴミ箱に送ってる部分が遅いのです。
ファイル1000個ぐらいで15分程度なので耐えられない遅さではないとは思いますが、 go で並列化出来るといいんだろうなと感じました。


余談ですが、ゴミ箱に入れる方法が分からなかったのでその部分は gomi を参考にさせてもらいました。
(AppleScript使って実現してるんですね。)

Link

navigator.sendBeaconを動かしてみた!!

What is navigator.sendBeacon?

navigator.sendBeaconってなんのこっちゃって感じなのでこれを読むと良い。

Beacon
日本語版: Beacon (日本語訳)

何がうれしいの!?って感じですが、例えばリンクをクリックした時にXMLHttpRequestでサーバーに情報を送っているようなケースで
サーバーのレスポンスが悪かったりするとサーバーにデータを送る前に遷移が完了してしまって取りこぼしてしまって正確なデータの収集が出来ないようなケースがあります。 また、サーバーからのレスポンスを待って遷移させるようなケースではサーバー側のレスポンスが悪いとページ遷移の妨げになりユーザーエクスペリエンスの低下につながります。

これをBeacon APIを使うことでユーザー・アクションを妨げることなくブラウザのバックグラウンドで処理を実行して確実にサーバーにデータを送れるようになります。
そして、送ったデータは広告なりアクセス解析なりなんなりと利用すればいいと思います。

サーバーを書いてみる

まずはサーバーを書いてみます。

package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    apachelog "github.com/gorilla/handlers"
    "os"
)

const (
    body string = `
<html>
<script>
  // emit non-blocking beacon to record state update
  function reportEvent(eventData) {
    navigator.sendBeacon('/collector', eventData);
  }

  // emit non-blocking beacon to record click on a link
  function reportClick(url) {
    var clickData = JSON.stringify({
      clickTo: url,
      clickAt: performance.now()
    });
    navigator.sendBeacon('/collector', clickData);
  }

  // emit non-blocking beacon when page transitions to background state
  document.addEventListener('visibilitychange', function() {
    if (document.visiblityState === 'hidden') {
      var analyticsData = buildSessionReport();
      navigator.sendBeacon('/collector', analyticsData);
    }
  });
</script>

<body>
 <a href='http://www.w3.org/' onclick='reportClick(this)'>
 <button onclick="reportEvent('button event!')">Click me</button>
</body>
</html>
`
    base64GifPixel = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
)

type BaseHandler struct{}

func (bh *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, body)
}

type BeaconHandler struct {
    BeaconImage []byte
    RequireTLS  bool
}

func (bh *BeaconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Println("send beacon")
    //if bh.RequireTLS  r.TLS == nil {
    // log.Printf("WARNING: Please Requst with https.\n")
    //}
    if s, err := ioutil.ReadAll(r.Body); err == nil {
        log.Printf("DEBUG: %#v\n", string(s))
    } else {
        log.Printf("DEBUG: Error %#v\n", err)
    }
    output := string(bh.BeaconImage)
    w.Header().Set("Content-Type", "image/gif")
    //w.Header().Set("Content-Length", string(len(output)))
    io.WriteString(w, output)
    return
}

func main() {
    port := 8000
    mux := http.NewServeMux()

    // set demo page handler
    bh := &BaseHandler{}
    mux.Handle("/hey", bh)

    // Convert image && set handdler
    output, _ := base64.StdEncoding.DecodeString(base64GifPixel)
    beacon := &BeaconHandler{
        BeaconImage: output,
    }
    mux.Handle("/collector",
        apachelog.CombinedLoggingHandler(os.Stderr, beacon))

    // Extra
    mux.Handle("/", http.RedirectHandler("/hey", http.StatusFound))


    fmt.Printf("Listen: %d\n", port)
    addr := fmt.Sprintf(":%d", port)
    srv := http.Server{
        Addr:     addr,
        Handler:  mux,
        ErrorLog: nil,
        //ReadTimeout:  10 * time.Second,
        //WriteTimeout: 10 * time.Second,
    }
    err := srv.ListenAndServe()
    if err != nil {
        log.Fatal(err)
    }
}

検証用のサーバースクリプトを用意したのでとりあえず、サーバーを起動してみよう。

% go run server.go
Listen: 8000

ブラウザからアクセスしてみる

navigator.sendBeaconが実装されているブラウザはあまり多くない。
caniuseで確認してみるとFirefoxChromeには実装されているがそれ以外のサポート状況はあまりよろしくない。
モバイル界隈も最新のAndroidのブラウザはサポートされているがiOS safariがサポートされていない状況となっている。
個人的にiOS safariがサポートされていないのは痛い状況で日本のWEB界隈だとiOS:Androidが7:3ぐらいなので7に実装されてないとnavigator.sendBeaconを利用する側のモチベーションに影響するんじゃないかと思う。
また、Can I useのページにも日本におけるサポートブラウザの比率が掲載されているがまだ 38.87% ということで半分にも満たない。
普及にはiOS Safariで実装がされることが必須のように思う。

Can I use... Support tables for HTML5, CSS3, etc

自分はFirefox 42からこのURLにアクセス。
アクセスしたらボタンを押して遷移してW3Cのページに移動しよう。
移動したら、コンソールに戻ってみたください。 下のようなログが落ちてるはずです。

% go run server.go
Listen: 8000

2015/12/13 02:34:45 send beacon
2015/12/13 02:34:45 DEBUG: "event!"
2015/12/13 02:34:45 send beacon
2015/12/13 02:34:45 DEBUG: "{\"clickTo\":{},\"clickAt\":2601.465}"

ここには詳細は書いてませんが仕様にはPOSTでリクエストすることとなってますので比較的大きなデータでも受け渡すことが可能です。

Beacon-Age ヘッダフィールドというものが定義されています。 これはユーザーアクションからサーバーに送るまで非同期で処理されるためアクションを起こした正確な時間をサーバー側で取得するためのヘッダーとなっています。 ただ、tcpdump使ってhttpの通信みた限りChrome,Firefoxともに実装されていないので頭の片隅に入ってればいいんじゃないかなと。

Link

Goで一定時間だけ立ち上がるサーバーを書いた

一定時間だけ立ち上がって自動的に終了するサーバーをGoで書いてみた。
あんまり用途はないのかもしれないが、例えばcronで毎日0:00から5分だけ立ち上がっていてそこにアクセスしてサーバーメトリクスを収集するみたいな使い方が出来るかもしれない。

いちおうコードの解説。
main関数のところだけコードをの詳細を書いてみる。
まずはじめに、runServer() でサーバーを立ち上げる。
次に、 time.Tick t で一定時間経過後にシグナルを送るようなselectループを作成する。
time.Tick et はただのデバッグ用のコードなので削除しても構わない。
最後に、 チャネル ch で入力を待って終了する。

func main() {
    go runServer()

    t := time.Tick(time.Duration(duration) * time.Second)
    et := time.Tick(time.Duration(1) * time.Second)
    go func() {
        for {
            select {
            case <-t:
                ch <- true
            case <-et:
                // debug code
                fmt.Printf("[%d] server running!\n", time.Now().Unix())
            }
        }
    }()
    _ = <-ch
    fmt.Println("Finished.")
}

試しに、3秒で終了するように動かしてた結果がこんな感じ。
きっちり、3回デバッグ用のコードが出力されて終了している。

% go run hey_server.go -duration=3
[1446923055] server running!
[1446923056] server running!
[1446923057] server running!
Finished.

以下、コード全体。

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "time"
)

type BaseHandler struct {
    Name string
}

func (bh *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if bh.Name != "" {
        fmt.Fprint(w, "Hey "+bh.Name+"!")
    } else {
        fmt.Fprint(w, "Hey World!")
    }
}

func runServer() {
    mux := http.NewServeMux()
    bh := &BaseHandler{Name: name}
    mux.Handle("/hey", bh)

    addr := fmt.Sprintf(":%d", 8080)
    err := http.ListenAndServe(addr, mux)
    if err != nil {
        log.Fatal(err)
    }
}

var ch = make(chan bool)

var name string
var duration int

func init() {
    flag.StringVar(&name, "name", "", "your name")
    flag.IntVar(&duration, "duration", 60, "working time")
    flag.Parse()
}

func main() {
    go runServer()

    t := time.Tick(time.Duration(duration) * time.Second)
    et := time.Tick(time.Duration(1) * time.Second)
    go func() {
        for {
            select {
            case <-t:
                ch <- true
            case <-et:
                // debug code
                fmt.Printf("[%d] server running!\n", time.Now().Unix())
            }
        }
    }()
    _ = <-ch
    fmt.Println("Finished.")
}

// https://gobyexample.com/select

Link

uokada.hatenablog.jp

influxdbをCLIから利用する

influxdb ver0.9を触っているけど、いろいろ変わりすぎているので公式ドキュメントしか頼っている。。。

ver0.6ぐらい触った時はこんな挙動だった?って思うことが沢山w

とりあえず、curlからデータを挿入してみる。

Write Syntax | InfluxDB

Specify Non-nanosecond Timestamps

Use the precision=[n,u,ms,s,m,h] query string parameter to supply a precision for the timestamps.

All timestamps are assumed to be Unix nanoseconds unless otherwise specified. If you provide timestamps in any unit other than nanoseconds, you must supply the appropriate precision in the URL query string. Use n, u, ms, s, m, and h for nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.

precisionパラメータでデータの精度を決めることが出来る。今回はテストなのでそこまで精度が必要ないので秒単位でデータを登録していく。

% curl -i -X POST 'http://localhost:8086/write?db=mydb&precision=s' \
  -d 'cput,host=server01,region=us-west value=0.64 1434055566'
HTTP/1.1 204 No Content
Request-Id: 5fa0e3de-8566-11e5-802b-000000000000
X-Influxdb-Version: 0.9.4.1
Date: Sat, 07 Nov 2015 15:44:04 GMT

バッチでデータを挿入することも出来るので大量データを挿入する場合はこっちでデータを挿入するのがベター。 ファイルのフォーマットは単発でデータを挿入する場合と一緒。

Write a Batch of Points with curl

You can also pass a file using the @ flag. The file can contain a batch of points, one per line. Points > must be separated by newline characters \n. Batches should be 5000 points or fewer for best performance.

これでもダメならUDPでデータ挿入なのかな。 Go製なのでパフォーマンスはかなり出るからこれを使うのはかなり先になりそう。

2015-10-13の艦これ戦果

5-2突破。編成はこんな感じでいった。

f:id:uokada:20151014073100j:plain

旗艦に軽空母正規空母x2、低速戦艦、重巡洋艦高速戦艦の順番。 軽空母を大破しにくいように旗艦に設定して、同じく戦艦を最後尾に戦艦を配置している。

制空権はまぁまぁ重視しているけど、BOSS戦は制空優勢ぐらいしか取れない編成で昼戦のみで十分S勝利出来た。

f:id:uokada:20151014073622j:plain

あと、ゲージ破壊時に夕雲さんGET。

f:id:uokada:20151014083932j:plain

初めての烈風GET && 摩耶改二ほか

f:id:uokada:20150922103725j:plain

【艦これ】データ厨が選ぶ!おすすめ開発レシピ | あ艦これ日和 - 艦これ攻略情報,プレイ日記

20/20/10/90のレシピで初めての烈風をGET!!
3月から始めたので半年で1つだと。。。。

f:id:uokada:20150922103935j:plain 摩耶がLv75まで成長したので改二に重巡洋艦で2隻目の改二GET。
うちには秋月型いないので防空お任せ出来る艦がやっと作れた〜


「水雷戦隊」バシー島沖緊急展開 編成と攻略 | 艦これ攻略日誌 ~艦ろぐ~

水雷戦隊」バシー島沖緊急展開も今週クリアしたけど、これ20回ぐらい2-2に行ってやっとBOSS戦 S勝利出来た!!
水雷戦隊編成だと補給船狩りには最適だけどBOSSに行かなすぎて辛い...