2017/07/11


Visual Studio Code で ssh 先のファイルを編集するには、Remote VSCode を使います。

Remote VSCode - Visual Studio Marketplace

A package that implements the Textmate's 'rmate' feature for VSCode.

https://marketplace.visualstudio.com/items?itemName=rafaelmaiolla.remote-vscode
なかなか仕組みが面白かったので紹介したいと思います。Remote VSCode は、実際は Sublime TextTextmate の rmate というコマンドの派生版です。どうやって ssh 先のファイルを編集するかというと、勘の良い方であれば以下のコマンドを見れば理屈は分かるはず。 $ ssh -R 52698:127.0.0.1:52698 user@example.org rmate /path/to/the/file.txt

rmate は Remote VSCode と通信してファイルの中身をやり取りします。実際には ssh でログインした際のポートフォワード機能によりリモート側のポート 52698 番で受け待ちすると同時に rmate コマンドを起動、ローカルで Remote VSCode と通信します。

rmate のプロトコルは至って簡単で、最初にファイルセットを送ります。

open
display-name: file.txt
data-on-save: yes
re-activate: yes
token: xxxxxxxxxxxxxxxx
data: 14

This is a file
.

以降は Remote VSCode が保存する度に以下のメッセージを送ってきます。

save
token: xxxxxxxxxxxxxxxx
data: 16
This is the file

面白い事にこの rmate、なぜだか色んな言語で実装されている。

Ruby version: https://github.com/textmate/rmate
Bash version: https://github.com/aurora/rmate
Perl version: https://github.com/davidolrik/rmate-perl
Python version: https://github.com/sclukey/rmate-python
Nim version: https://github.com/aurora/rmate-nim
C version: https://github.com/hanklords/rmate.c
Node.js version: https://github.com/jrnewell/jmate

いや、そんなにいらんやろw と思いつつも golang 版が無かったので作った。おとなげない。

GitHub - mattn/gomate

README.md gomate Edit files from an ssh session in TextMate/VSCode Usage 1 gomate /path/to/the/file....

https://github.com/mattn/gomate

まぁ、ssh してリモートで vim 使えばいいじゃんって思うけど、どうしても Visual Studio Code じゃないと嫌やねんって人はどうぞ。

Posted at by



2017/06/29


今日とある場所で虫が入り込む瞬間を見た。虫といってもバグの方。それはプログラマ向けの Q&A サイトで始まった。質問の内容はこうだ。

【防除用医薬部外品】アース渦巻香 蚊取り線香 [12時間長持ち ジャンボ50巻缶入] 単品 ジャンボ50巻缶入り ビャクダン コイル 【防除用医薬部外品】アース渦巻香 蚊取り線香 [12時間長持ち ジャンボ50巻缶入] 単品 ジャンボ50巻缶入り ビャクダン コイル

アース製薬 ヘルスケア&ケア用品 / ¥941 (¥19 / 個) (1970年01月01日)
 
発送可能時間:

文字列には0または4がだけが含まれる。文字列は 4 から始まり、例えば 440, 44, 40, 4400, 4440 など、これらは正しいとするが 404 は正しくない。今のところ、私は 0 の直後に 4 が現れるかどうかでチェックしている。これは果たして効率的だろうか。

始め僕はこの質問文をちゃんと読んでおらず、正規表現を使ってこれを実装した。

package main

import (
    "regexp"
)

func check(s stringbool {
    return regexp.MustCompile(`^4+0*$`).MatchString(s)
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

でも質問をよく見たら彼は効率的かどうかを気にしていた。確かにこのお題で正規表現は無い。僕は慌てて以下のコードを付け添えた。

package main

func check(s stringbool {
    i := 0
    r := []rune(s)
    for i = 0; i < len(r); i++ {
        if r[i] != '4' {
            break
        }
    }
    if i == 0 {
        return false
    }
    for ; i < len(r); i++ {
        if r[i] != '0' {
            return false
        }
    }
    return true
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

いずれのパッケージにも依存しておらく、おそらくちゃんと動くコードだろう。

フマキラー 蚊取り線香 ジャンボ 箱 50巻 フマキラー 蚊取り線香 ジャンボ 箱 50巻

フマキラー ヘルスケア&ケア用品 / ¥887 (¥18 / 個) (1970年01月01日)
 
発送可能時間:

その後、周りのオーディエンスが質問した彼に「どんなケースか良く分からないな、コードを見せてくれる?」と言った。そして彼は以下のコードを見せてくれた。

package main

import (
    "fmt"
    "strings"
)

func validate(str stringbool {

    if strings.HasPrefix(str, "4") {
        for i := 0; i < len(str)-1; i++ {
            if (str[i] == '0'&& (str[i+1== '4') {
                return false
            }
        }

    } else {
        return false
    }

    return true
}

func main() {

    data := []string{"4""44""4400""4440""404""004"}
    for _, val := range data {
        fmt.Println(validate(val))
    }
}

なるほど彼が最初に言ってた通り「0 の直後に 4 が現れる事でチェック」している。一見このコードは正しそうに見える。でもこのコードは「406」の様な文字列で正しく機能しない。彼にそれを伝えたところ、彼は「@mattn -1 最初に 0 と 4 しか無いっていったじゃん」と返してきた。

僕はここで「あー、バグが混入するタイミングはここなんだ」と思った。例えばこの関数が「4 から始まり 0 が 0 個以上続く文字列をチェックする」関数だとして他のユーザに配られるとする。それを譲り受けた開発者はそれを使ってテストする。この時点でその開発者は「もちろん 406 みたいな文字列も弾いてくれる」と信じてしまうだろう。こうやってバグってのは混入するんだ、と思った。まぁ、もしかしたら彼のキーボードには 0 と 4 とリターンキーしか付いていなかったのかもしれない。

村田屋産業 防蚊 ブラック 本体サイズ:約16×17×15cm 村田屋産業 防蚊 ブラック 本体サイズ:約16×17×15cm

村田屋産業(Murataya Sangyo) ホーム&キッチン / ¥2,050 (1970年01月01日)
 
発送可能時間:

Posted at by



2017/06/22


要求仕様から工数を出す側から言うと「ブラウザのダウンロード画面に進捗出てるから要らないでしょ」と言いたい所でしたが「出来ないのか」と言われると「出来るもん」と言わざると得ないエンジニア魂。

JavaScript - ブラウザから、ファイルをダウンロードしている途中で、プログレスバーを実装したい。完了したら、プログレスバーを閉じたい。(81363)|teratail

前提・実現したいこと javaScript/HTML/CSSを利用しております。 目的は、ブラウザから、ファイルをダウンロードしている途中で、プログレスバーを実装したい。完了したら、プログレスバーを閉...

https://teratail.com/questions/81363

通常、ブラウザからファイルをダウンロードする際は javascript からは制御できません。サーバからバイト列を JSON で Range っぽく返して最後に data スキームでダウンロードダイアログを出す、といったニッチなテクニックでも事も出来なくないですがブラウザにメモリを保持してしまって大きいファイルだとハングしかねない等の問題が発生します。で、どうやるかというとまずはサーバの処理

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "sync"

    "github.com/google/uuid"
    "github.com/labstack/echo"
)

var (
    m = &sync.Map{}
)

// ダウンロードの進捗を JSON で返す
func stat(c echo.Context) error {
    ck, err := c.Cookie("download-progress")
    if err != nil {
        log.Println(err)
        return err
    }
    progress := 0
    v, ok := m.Load(ck.Value)
    if ok {
        if vi, ok := v.(int); ok {
            progress = vi
        }
    }
    return c.JSON(http.StatusOK, &struct {
        Progress int `json:"progress"`
    }{
        Progress: progress,
    })
}

// クライアントにデータを送信しつつ進捗を更新
func download(c echo.Context) error {
    id := uuid.New().String()
    c.SetCookie(&http.Cookie{
        Name:  "download-progress",
        Value: id,
    })
    f, err := os.Open("ubuntu-17.04-server-amd64.iso")
    if err != nil {
        log.Println(err)
        return err
    }
    defer f.Close()
    st, err := f.Stat()
    if err != nil {
        log.Println(err)
        return err
    }
    total := st.Size()
    rest := total
    m.Store(id, 0)

    w := c.Response().Writer
    w.Header().Set("Content-Disposition""attachment")
    w.Header().Set("Content-Length", fmt.Sprint(total))
    for {
        var b [4098]byte
        n, err := f.Read(b[:])
        if err != nil {
            break
        }
        _, err = w.Write(b[:n])
        if err != nil {
            break
        }
        rest -= int64(n)
        m.Store(id, int((total-rest)*100/total))
        if total <= 0 {
            break
        }
    }
    m.Store(id, nil)
    return nil
}

func main() {
    e := echo.New()

    e.GET("/stat", stat)
    e.GET("/download", download)

    e.Static("/""static")
    e.Logger.Fatal(e.Start(":8989"))
}

そしてクライアント側の処理

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>download</title>
<script>
window.addEventListener('load'function() {
  function progress() {
    fetch("/stat"{
      'credentials'"same-origin"
    }).then(function(response) {
      return response.json();
    }).then(function(json) {
      document.querySelector('#progress').textContent = json.progress + "%";
      if (json.progress < 100) setTimeout(progress, 1000);
    })
  }
  document.querySelector('#download').addEventListener('click'function() {
    progress();
    return true;
  });
}false);
</script>
</head>
<body>
    <p>
        <span id="progress"></span>
    </p>
    <a id="download" href="/download">Download</a>
</body>
</html>

ダウンロードが始まったらランダムIDでクッキーを返送し、そのIDでステータスの要求を受け付ける。ダウンロードは細かい単位で行い都度進捗を更新する。こうすればダウンロードが始まれば進捗がパーセンテージで表示され、終了すればタイマーが止まる。プログレスバー表示やダウンロードをキャンセルした際の処理はめんどくさいので実装してないですが分かりますよね。あとダウンロードが終わったら m から破棄しないと何時かサーバがパンクしますよっと。

ダウンロードの進捗表示は出来なくはない。ただ、これだけは言っておきたい。

実装は、仕事でやるならタダじゃない(575)
Posted at by