新月実装開発部

815623ad anonymous 2015-12-11 15:19
ノードの情報をデータベースで管理するように変更。
明日こそスレの取得を試してみよう。
クローラーさえできればゲートウェイの実装に取り掛かれるので楽しみ:grin:
eb88658c anonymous 2015-12-12 00:03
@markdown
ファイル用のテーブルを作成。またどうせ後で変更するんだろうけど…
```clojure
(defn create-files-table
  [db-spec]
  (let [{:keys [id blob varchar varchar-ignorecase-unique]} (db-types db-spec)]
    (sql/db-do-commands
      db-spec
      (sql/create-table-ddl
        :files
        [:id           id]
        [:file_name    varchar-ignorecase-unique "NOT NULL"]
        [:application    varchar-ignorecase-unique "NOT NULL"]
        [:time_created "TIMESTAMP NULL"]
        [:time_first_post "TIMESTAMP NULL"]
        [:time_updated "TIMESTAMP NULL"]
        [:num_records "INTEGER DEFAULT 0"]
        [:num_deleted_records "INTEGER DEFAULT 0"]))))
```
6edf8b07 anonymous 2015-12-12 07:10
スレッドの取得に一応成功。次は/headを使ってもっと効率良くしてみようっと。
43404a01 anonymous 2015-12-12 11:31
@markdown
どうやらクローラーができたっぽい。それでは早速ポチッとな。

```clojure
(defn get-files-with-recent-command []
  (let [records (clojure.string/split (apply str (pmap #(recent %1 "0-") @active-nodes)) #"\n")
        records (remove #(not (re-find #"^[0-9]+<>[0-9a-f]{32}<>thread_[0-9A-F]+(<>.*)?$" %)) records)
        file-names (map #(second (re-find #"^[0-9]+<>[0-9a-f]{32}<>(thread_[0-9A-F]+)(<>.*)?$" %)) records)
        file-names (clojure.set/difference (into #{} file-names) known-corrupt-files)]
    file-names))

(defn download-thread-from-node
  ([node-name file-name]
   (download-thread-from-node node-name file-name "0-"))

  ([node-name file-name range]
   (timbre/debug "download-thread-from-node:" node-name file-name range)
   (if-not (valid-node-name? node-name)
     (throw (IllegalArgumentException. "Invalid node name.")))
   (if-not (valid-file-name? file-name)
     (throw (IllegalArgumentException. "Invalid file name.")))
   (if-not (valid-range? range)
     (throw (IllegalArgumentException. "Invalid range.")))

   (try
     (let [file-id (db/get-file-id file-name)
           existing-records (and file-id (db/get-all-records-in-file-without-bodies file-id))]
       (if (and (= range "0-")
                existing-records
                (pos? (count existing-records)))
         ; Use /head to find missing records.
         (let [file (:body (client/get (str "http://" node-name "/head/" file-name "/" range) http-params))
               file (clojure.string/replace file #"(?m)^(?![0-9]+<>[0-9a-f]{32}).*$" "")
               file (clojure.string/replace file #"\r" "")
               file (clojure.string/replace file #"\n+" "\n")
               records (remove #(zero? (count %)) (clojure.string/split-lines file))
               records (map #(let [match (re-find #"^([0-9]+)<>([0-9a-f]{32})" %)]
                              {:stamp (Integer/parseInt (nth match 1)) :record-id (nth match 2)})
                              records)
               existing-records (map #(identity {:stamp (:stamp %) :record-id (:record-id %)}) existing-records)
               records (clojure.set/difference (into #{} records) (into #{} existing-records))]
           (if (empty? records)
             0
             (let [stamps (map :stamp records)
                   oldest (apply min stamps)
                   newest (apply max stamps)]
               (download-thread-from-node node-name file-name (str oldest "-" newest)))))

         ; Use the supplied range.
         (let [file (:body (client/get (str "http://" node-name "/get/" file-name "/" range) http-params))
               file (clojure.string/replace file #"(?m)^(?![0-9]+<>[0-9a-f]{32}<>).*$" "")
               file (clojure.string/replace file #"\r" "")
               file (clojure.string/replace file #"\n+" "\n")
               records (remove #(zero? (count %)) (clojure.string/split-lines file))]
           (dorun
             (pmap
               #(try
                 (let [match (re-find #"^([0-9]+)<>([0-9a-f]{32})<>(.*)$" %)
                       stamp (nth match 1)
                       record-id (nth match 2)
                       body (nth match 3)]
                   (db/add-record file-id stamp record-id body))
                 (catch Throwable _ (timbre/debug (str "download-thread-from-node: Record skipped: " %))))
               records))
           ;(if-not (valid-file? file)
           ;  (throw (Exception. "Invalid file.")))
           (count records))))
         (catch Exception e
           (timbre/error e)
           nil))))

(defn download-thread-from-all-active-nodes
  ([file-name]
   (download-thread-from-all-active-nodes file-name "0-"))

  ([file-name range]
   (if-not (valid-file-name? file-name)
     (throw (IllegalArgumentException. "Invalid file name.")))
   (if-not (valid-range? range)
     (throw (IllegalArgumentException. "Invalid range.")))
   (dorun
     (map
       #(download-thread-from-node % file-name range)
       (shuffle @active-nodes)))
   true))

(defn crawl-nodes []
  (timbre/debug "crawl-nodes")
  (try
    (let [file-names (get-files-with-recent-command)]
      (dorun (pmap #(db/add-file %) file-names)))
    (dorun
      (pmap
        download-thread-from-all-active-nodes
        (map :file-name (db/get-all-files))))
    (catch Throwable t
      (timbre/error t)
      nil)))
```
afd30e3f anonymous 2015-12-12 15:29
@markdown
クローラがようやくちゃんと動くようになりました。
/getをうまく使わないとすぐタイムアウトになっちゃいまね、これ。
/headでレスの欠損を調べて、一括でダウンロードできなかったスレは
レコードを個別にダウンロードしています。
```clojure
(defn get-files-with-recent-command []
  (let [records (clojure.string/split (apply str (pmap #(recent %1 "0-") @active-nodes)) #"\n")
        records (remove #(not (re-find #"^[0-9]+<>[0-9a-f]{32}<>thread_[0-9A-F]+(<>.*)?$" %)) records)
        file-names (map #(second (re-find #"^[0-9]+<>[0-9a-f]{32}<>(thread_[0-9A-F]+)(<>.*)?$" %)) records)
        file-names (clojure.set/difference (into #{} file-names) known-corrupt-files)]
    file-names))

(defn download-thread-from-node
  ([node-name file-name]
   (download-thread-from-node node-name file-name "0-"))

  ([node-name file-name range]
   (timbre/debug "download-thread-from-node:" node-name file-name range)
   (if-not (valid-node-name? node-name)
     (throw (IllegalArgumentException. "Invalid node name.")))
   (if-not (valid-file-name? file-name)
     (throw (IllegalArgumentException. "Invalid file name.")))
   (if-not (valid-range? range)
     (throw (IllegalArgumentException. "Invalid range.")))

   (try
     (let [file-id (db/get-file-id file-name)
           existing-records (and file-id (db/get-all-records-in-file-without-bodies file-id))]
       (if (and (= range "0-")
                existing-records
                (pos? (count existing-records)))
         ; Use /head to find missing records.
         (let [file (:body (client/get (str "http://" node-name "/head/" file-name "/" range) http-params))
               file (clojure.string/replace file #"(?m)^(?![0-9]+<>[0-9a-f]{32}).*$" "")
               file (clojure.string/replace file #"\r" "")
               file (clojure.string/replace file #"\n+" "\n")
               records (remove #(zero? (count %)) (clojure.string/split-lines file))
               records (map #(let [match (re-find #"^([0-9]+)<>([0-9a-f]{32})" %)]
                              {:stamp (Integer/parseInt (nth match 1)) :record-id (nth match 2)})
                              records)
               existing-records (map #(identity {:stamp (:stamp %) :record-id (:record-id %)}) existing-records)
               records (clojure.set/difference (into #{} records) (into #{} existing-records))]
           (if (empty? records)
             0
             (let [stamps (map :stamp records)
                   oldest (apply min stamps)
                   newest (apply max stamps)]
               (download-thread-from-node node-name file-name (str oldest "-" newest))
               (let [existing-records (map #(identity {:stamp (:stamp %) :record-id (:record-id %)}) existing-records)
                     records (clojure.set/difference (into #{} records) (into #{} existing-records))
                     stamps (map :stamp records)]
                 (dorun (map #(download-thread-from-node node-name file-name (str %)) stamps))))))

         ; Use the supplied range.
         (let [file (:body (client/get (str "http://" node-name "/get/" file-name "/" range) http-params))
               file (clojure.string/replace file #"(?m)^(?![0-9]+<>[0-9a-f]{32}<>).*$" "")
               file (clojure.string/replace file #"\r" "")
               file (clojure.string/replace file #"\n+" "\n")
               records (remove #(zero? (count %)) (clojure.string/split-lines file))]
           (dorun
             (pmap
               #(try
                 (let [match (re-find #"^([0-9]+)<>([0-9a-f]{32})<>(.*)$" %)
                       stamp (nth match 1)
                       record-id (nth match 2)
                       body (nth match 3)]
                   (db/add-record file-id stamp record-id body))
                 (catch Throwable _ (timbre/debug (str "download-thread-from-node: Record skipped: " %))))
               records))
           ;(if-not (valid-file? file)
           ;  (throw (Exception. "Invalid file.")))
           (count records))))
         (catch Exception e
           (timbre/error e)
           nil))))

(defn download-thread-from-all-active-nodes
  ([file-name]
   (download-thread-from-all-active-nodes file-name "0-"))

  ([file-name range]
   (if-not (valid-file-name? file-name)
     (throw (IllegalArgumentException. "Invalid file name.")))
   (if-not (valid-range? range)
     (throw (IllegalArgumentException. "Invalid range.")))
   (dorun
     (map
       #(download-thread-from-node % file-name range)
       (shuffle @active-nodes)))
   true))

(defn crawl-node [node-name]
  (timbre/debug "crawl-node:" node-name)
  (try
    (dorun
      (map
        #(if (some #{ node-name } @active-nodes)
          (download-thread-from-node node-name %))
        (shuffle (map :file-name (db/get-all-files)))))
    (catch Throwable t
      (timbre/error t)
      nil)))

(defn crawl-nodes []
  (timbre/debug "crawl-nodes")
  (try
    (comment let [file-names (get-files-with-recent-command)]
      (dorun (pmap #(db/add-file %) file-names)))
    (dorun
      (pmap crawl-node (shuffle @active-nodes)))
    (catch Throwable t
      (timbre/error t)
      nil)))
```
0b2675c7 anonymous 2015-12-13 11:09
@markdown
レコードの本文をVARCHARで保存してたらメモリ不足になったのでBLOBに変更してやり直し。結構たくさんたまりました。
```
(count (ju.db.core/get-all-records))
=> 252439
```
49fdb310 anonymous 2015-12-13 11:18
@markdown
まだたまにエラーが出るなあ。レコードの転送に失敗してるのかしらん。
```
java.io.EOFException: Unexpected end of ZLIB input stream
java.util.zip.ZipException: invalid distance too far back
```
e58230ac anonymous 2015-12-13 15:14
>>ce639e27
それネットワークが不安定にならない?
ab5d938a anonymous 2015-12-13 16:22
>>e58230ac
003d5e73 anonymous 2015-12-13 16:41
>>e58230ac
/pingと/joinだけなら負荷も全然軽いし大丈夫でしょう。
8cefa68a anonymous 2015-12-13 19:21
>>003d5e73
joinはネットワークの形を変える機能だよ
539165e8 anonymous 2015-12-13 19:32
>>8cefa68a
というより、/joinする相手から/join済みノードを追い出す機能
e709bb1f anonymous 2015-12-14 04:23
>>539165e8
それはどこにも書いてなかったような…
どうしようかな。
03b62f6c anonymous 2015-12-14 14:14
>>e709bb1f
実装上の問題です。
sakuはjoinが来ると、1ノード追い出します。
https://github.com/shingetsu/saku/blob/25c932c992d5fabdd2bff9cf3302284d7a5bbad2/shingetsu/server_cgi.py#L154
45c18b45 anonymous 2015-12-14 15:38
>>03b62f6c
ははあ、一番最初に/joinしたノードが追い出されるわけですね。
ce98f0b8 anonymous 2015-12-14 15:42
レスの表示に成功したのでテンション上がってきたw
とりあえずクロールで集めたデータをきちんと閲覧できるようにしようっと。

ファイルの数: 1114
レコードの数: 253077
キャッシュサイズ: 5319MB
670404d0 anonymous 2015-12-14 15:45
効率の良いクローラーを書くのむずい
30fbf9b9 anonymous 2015-12-14 18:42
>>45c18b45
1. A-B となってるところに X がjoinしようとする
2. A-X となってBが切り離される
3. A-X-B として繋ぎ直す
5cb62859 anonymous 2015-12-14 20:39
こういうの作ってます
http://133.130.115.168/
0322c961 anonymous 2015-12-15 02:31
>>670404d0
結局朔のAPIと/headを使って探索ノードを虱潰しして抜けたレスを探していくしかないという…
98eaa538 anonymous 2015-12-15 02:33
>>30fbf9b9
朔は隣接ノードの数を制限してるから再度/joinしたときに結局別のノードが追い出される。
e0ee4ae1 anonymous 2015-12-15 02:34
>>5cb62859
一緒に頑張りませう。
a3e4dd78 anonymous 2015-12-15 02:42
>>03b62f6c
確かにそうしないと新しいノードが/join出来無いですよね。
定期的に隣接ノードを入れ替えればいいのかしら。
f834edbb anonymous 2015-12-15 13:30
/haveと/headと/getができたっぽい。
テストは一通りしたけど、さすがにオンラインにするのは緊張するなあ。
d1d8d3bc anonymous 2015-12-15 13:37
> >>7a3ee89b
> http://shingetsu.info/protocol/protocol-0.7.d1
> > /get/ファイル名/時刻引数
> > 「時刻/識別子」指定した時刻と識別子のレコード
[[新月の開発/1fb56e12]]

これ実装するの忘れてた! やばいやばい。
ef6bca7c anonymous 2015-12-15 15:14
/updateができたけどうまくいくかな~
855d46b5 anonymous 2015-12-16 16:11
/updateはちゃんと動くようになりました。
これで/recent以外のプロコトルコマンドは全て実装したことになります。
/recentをちょっと実装してみようかな。
3012f9cb anonymous 2015-12-17 03:17
/recentをちゃんと実装するのに新しいテーブルがデータベースに必要になったので、この機会にデータベースの構造を見なおしてスレッドをクロールしなおしました。ここらへんはデータの再取得がすぐできる新月は非常に便利です。いずれMySQLやPostgreSQLでもテストしたいんですけど、いつやろうかな。
d66e9169 anonymous 2015-12-17 16:42
クローラーができてupdaterも出来てよしUI
と思った矢先他のノードへの通信が出来ない現象が発生してる
saku win node 全部怪しすぎて分からん
sakuはUTF8 ダメ言ってるし
msのエラーページが出てくるし
nodeもうまくいう事聞かんし
とほほ…
fd8227b2 >>5cb62859 2015-12-17 20:49
フロントエンドがだいぶ軽くなった。
今後は安定版のリリースを目指す。それと、フロントエンドは機能追加したりデザインをなんとかする…
805bb9c5 anonymous 2015-12-17 21:36
お、頑張ってますね~
Clojureでの新月の新実装「需」はあともうちょっとでUIの作成に取り掛かれるところまで来ました。
d6be29e3 anonymous 2015-12-18 00:28
>>c6157ad8
ノード間の通信が出来ないから使えないと思う
ブラウザでは普通に動作する
まあ時間かかりそうだし時間のある時にするよ
bc2be153 anonymous 2015-12-18 00:41
>>d6be29e3
ノードを動かしているPCのブラウザからならちゃんとテストできますよ。

> ブラウザでは普通に動作する

朔がリクエストのヘッダを見て弾いているのかもしれませんね。
6d4f62c0 anonymous 2015-12-18 01:35
>>d6be29e3
それ心当たりある
sakuはcontent-typeヘッダーをつけていないのにgzip圧縮したレスポンスを返してくるんですが、対処してますか?
ff24acd8 anonymous 2015-12-18 04:45
「需」のプロコトルコマンドの実装がほぼ完了しました。
/recentは時刻引数の処理を真面目に実装すると結構面倒くさいです。

ゲートウェイのウェブページの作成をサーバーで行うかクライアントで行うかかなり迷ったんですが、結局クライアントサイドでClojureScriptで作成することにしました。これでページ間の遷移がかなりスムーズになるはずです。URLの問題はHTML5のpushState()で解決する予定です。

Manipulating the browser history
https://developer.mozilla.org/en-US/docs/Web/API/History_API
ca58f444 anonymous 2015-12-18 09:31
なんかエラー出たので報告

sakuのトレース(抜粋)
環境:win7 pyhton3.5 saku4.6.1

Exception happened during processing of request from ('127.0.0.1', 51759)

AppData\Local\Programs\Python\Python35-32\lib\http\server.py", line 931, in send_head
    return self.run_cgi()
shingetsu\LightCGIHTTPServer.py", line 253, in run_cgi
    cgiobj.start()
shingetsu\basecgi.py", line 103, in start
    self.run()
shingetsu\server_cgi.py", line 69, in run
    self.do_recent(path)
shingetsu\server_cgi.py", line 246, in do_recent
    fp = self.output()
shingetsu\server_cgi.py", line 194, in output
    return TextIOWrapper(fp, 'utf-8', 'replace')
AttributeError: 'BodyFilter' object has no attribute 'readable'
13724a8f anonymous 2015-12-18 12:55
>>ca58f444
ここは新実装のスレなので、朔の話はこっちのほうがいいんじゃないかな。
[[新月の開発]]
2806bd49 anonymous 2015-12-18 14:28
>>ca58f444 です
>>6d4f62c0 の言う事が関係してました

リクエスト時に accept-encoding が指定されていないとsakuが >>d66e9169 のエラーを出して応答しなくなるようです
ずっとnode側の問題だと思ってやってたからすごく時間を無駄にした気がする
8086a12e anonymous 2015-12-18 14:52
@markdown
>>2806bd49
本当だ。プロトコルの説明書にはaccept-encodingが必須とは書いてないからこれは変だね。
```python
        # HTTP_* headers require by SAKU
        env["HTTP_ACCEPT_LANGUAGE"] = \
            self.headers.get("Accept-Language", "")
        env["HTTP_ACCEPT_ENCODING"] = \
            self.headers.get("Accept-Encoding", "")
        env["HTTP_HOST"] = self.headers.get('host', '')
        env["HTTP_REFERER"] = self.headers.get("Referer", "")
        if 'X-Forwarded-For' in self.headers:
            env['HTTP_X_FORWARDED_FOR'] = self.headers['X-Forwarded-For']
```

https://github.com/shingetsu/saku/blob/5eaec8229dc33bbb42aac05847370011040de840/shingetsu/LightCGIHTTPServer.py

> ただし、gzip 形式で圧縮したメッセージをノードに送信する場合、 通信相手ノードはリクエストのメッセージヘッダを解析し、 ノードがgzip形式で圧縮されたメッセージを解釈できるかどうか 判断しなければならない。

http://shingetsu.info/protocol/protocol-0.7.d1
506094cd anonymous 2015-12-18 17:00
やっと通信終わった
これからUI(`・ω・´)ゞ
c7edc22a anonymous 2015-12-18 21:29
>>8086a12e
修正しました
e037b0bd anonymous 2015-12-19 03:28
>>c7edc22a
乙です乙です
dbb00625 >>5cb62859 2015-12-19 22:02
添付ファイルをサポートできた
リポジトリはここ https://github.com/yuuki0xff/mika
一応デバッグ用のdockerコンテナも用意しています。誰かテストしてくれる人いますか?
d0dbe869 anonymous 2015-12-19 23:35
>>dbb00625
これでしたっけ。頑張ってますね~
http://133.130.115.168/
ぜひ試してみたいけど時間がないのが残念…
80b4b0f8 anonymous 2015-12-20 00:19
>>d0dbe869
それは残念

そういや、使い方が書いてないのでここに書いておきますね。
dockerとtsc(typescriptのコンパイラ)とscssが必要

./docker/build.sh
tsc ./static/*.ts
scss ./static/*.scss
./core/settings.pyをテキストエディタで開き、NODE_NAME変数を設定
80番ポートを開放

あとは./docker/control.sh autoを実行してhttp://localhost/にアクセスすると…使えるようになっているはず
ログは./log/に溜まっていくはずです。
停止は./docker/control.sh kill all
c119ba32 anonymous 2015-12-20 06:17
>>80b4b0f8
Pythonとtypescriptか〜

> http://133.130.115.168/

このノード、うちの環境からは見えないけどどうなってるんだろう…
d4bbd0c9 anonymous 2015-12-20 06:20
>>d0dbe869
このノードからChrome 47で書き込みしてみたけど、書き込み後に画面がリフレッシュされないね。
923b1b94 anonymous 2015-12-20 06:37
>>c119ba32
あ、これはうちの朔や合からは見えていないってことね。
d7be1f6e anonymous 2015-12-20 06:48
「需」のページ遷移の処理を大幅に改善しました。
reagent.coreのイベントの通知の処理がきちんと文章化されていなかったので手間取りましたが、これで一安心です。
9f2e61ac anonymous 2015-12-20 08:19
>>d4bbd0c9
修正しました。
>>923b1b94
しばらく動かしていれば見えてきませんか?

Top of this page. | <<last <<new 0 1 2 3 | Archive | Mobile

limit: 1536KB

(新月実装開発部/200/0.1MB)

Powered by shinGETsu.