Chat Ops はじめました?
昨年末から、担当するプロジェクトが変わり、新たにチームを編成することになったので、今度の開発環境では、ぜひともメールをチャットに置き換えたいと思い、 Let's Chat を社内の開発サーバに導入してみています。
別に Chat Ops にするつもりはなかったですが、 Trac から通知するプラグインを作成しているので、全面的に導入を狙ってみます。
導入手手順はおいおいまとめるとして、先行導入で分かった問題として、 Let's Chat はデスクトップ通知が ON/OFF しか選べないため、人によっては、 ON にした場合に通知で気が散るというものがあります。
リアルタイムにフォローをするため、私はすべてのやり取りの通知がほしいですが、メンバによっては、メンションありの場合だけ通知できるようになると便利です。
というわけで、 0.4.5 をベースにデスクトップ通知をメンションありの場合だけできるように設定を拡張してみました。
diff --git a/app/core/account.js b/app/core/account.js index 539eb4e..baa100e 100644 --- a/app/core/account.js +++ b/app/core/account.js @@ -39,6 +39,9 @@ AccountManager.prototype.update = function(id, options, cb) { if (options.email) { user.email = options.email; } + if (options.hasOwnProperty('notificationsMentionedOnly')) { + user.notificationsMentionedOnly = options.notificationsMentionedOnly; + } if (options.username && options.username !== user.username) { var xmppConns = this.core.presence.system.connections.query({ diff --git a/app/models/user.js b/app/models/user.js index 6eceda5..2d3b335 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -86,6 +86,10 @@ var UserSchema = new mongoose.Schema({ type: String, trim: true }, + notificationsMentionedOnly: { + type: Boolean, + default: false + }, rooms: [{ type: ObjectId, ref: 'Room' @@ -271,7 +275,8 @@ UserSchema.method('toJSON', function() { lastName: this.lastName, username: this.username, displayName: this.displayName, - avatar: this.avatar + avatar: this.avatar, + notificationsMentionedOnly: this.notificationsMentionedOnly }; }); diff --git a/media/js/views/modals.js b/media/js/views/modals.js index 6ac2c12..48c02df 100644 --- a/media/js/views/modals.js +++ b/media/js/views/modals.js @@ -167,7 +167,8 @@ window.LCB.NotificationsModalView = Backbone.View.extend({ events: { - 'click [name=desktop-notifications]': 'toggleDesktopNotifications' + 'click [name=desktop-notifications]': 'toggleDesktopNotifications', + 'click [name=notifications-mentionedonly]': 'toggleNotificationsMentionedOnly' }, initialize: function() { this.render(); @@ -178,17 +179,31 @@ .siblings().hide(); if (!notify.isSupported) { $input.attr('disabled', true); - // Welp we're done here - return; } - if (notify.permissionLevel() === notify.PERMISSION_GRANTED) { + else if (notify.permissionLevel() === notify.PERMISSION_GRANTED) { $input.find('.enabled').show() .siblings().hide(); } - if (notify.permissionLevel() === notify.PERMISSION_DENIED) { + else if (notify.permissionLevel() === notify.PERMISSION_DENIED) { $input.find('.blocked').show() .siblings().hide(); } + + var $input = this.$('[name=notifications-mentionedonly]'); + $input.find('.disabled').show() + .siblings().hide(); + $.get('./notifications', function(data) { + if (data.status == 'error') { + $input.find('.disabled').show() + .siblings().hide(); + return; + } + + if (data.mentionedOnly) { + $input.find('.enabled').show() + .siblings().hide(); + } + }); }, toggleDesktopNotifications: function() { var that = this; @@ -198,6 +213,18 @@ notify.requestPermission(function() { that.render(); }); + }, + toggleNotificationsMentionedOnly: function() { + var that = this; + if (!notify.isSupported) { + return; + } + $.post('./notifications/mentionedOnly', function(data) { + if (data.status == 'error') { + return; + } + that.render(); + }); } }); diff --git a/media/js/views/window.js b/media/js/views/window.js index f7cda80..1809f40 100644 --- a/media/js/views/window.js +++ b/media/js/views/window.js @@ -168,10 +168,15 @@ notify.config({ pageVisibility: false }); + var that = this; this.client = options.client; this.rooms = options.rooms; $(window).on('focus blur unload', _.bind(this.onFocusBlur, this)); this.rooms.on('messages:new', this.onNewMessage, this); + + $.get('./notifications', function(data) { + that.notificationsMentionedOnly = data.mentionedOnly; + }); }, onFocusBlur: function(e) { this.focus = (e.type === 'focus'); @@ -193,6 +198,10 @@ notify.permissionLevel() != notify.PERMISSION_GRANTED) { return; } + if ( !message.mentioned && + this.notificationsMentionedOnly) { + return; + } var roomID = message.room.id, avatar = message.owner.avatar, diff --git a/templates/includes/modals/notifications.html b/templates/includes/modals/notifications.html index d9e5d81..d992943 100644 --- a/templates/includes/modals/notifications.html +++ b/templates/includes/modals/notifications.html @@ -20,6 +20,17 @@ <br /><small><$ __('Please check your browser settings') $></small> </span> </button> + <button name="notifications-mentionedonly" class="btn btn-info" style="display: block; margin-top: 10px;"> + <span class="enabled"> + Notify mentioned messages only + </span> + <span class="disabled"> + Notify any messages + </span> + <span class="blocked"> + Select notifications are <strong>blocked</strong> + </span> + </button> </div> </div> </div>
全体の設定は以下のようにしました。
--- a/settings.yml +++ b/settings.yml rooms: private: true + notifications: + mentionedOnly: true
この修正で、とりあえずユーザ毎にメンションなしのメッセージを通知するか否かを設定できるようになりました。
あと、ついでに履歴検索で正規表現を使えるように修正しました。( 人生楽しまなきゃもったいない!: Let's Chatを入れてみた からもろパクリですが)
diff --git a/app/core/messages.js b/app/core/messages.js index 24bb51f..5471257 100644 --- a/app/core/messages.js +++ b/app/core/messages.js @@ -90,7 +90,8 @@ MessageManager.prototype.list = function(options, cb) { } if (options.query) { - find = find.find({$text: {$search: options.query}}); + var r = new RegExp(options.query, "i") + find = find.find({"text": {$regex:r}}); } if (options.expand) {
デスクトップ通知については、本家の Slack が充実しているらしいので、修正を続けて、機能が充実したら master への PR も出してみたいな。
cherrypy に trac を載せた話
どういうこと?
仕事では受託開発をしており、プロジェクトにより開発環境が変わるのでポータブルなツールを利用することを心がけています。
最近は作業の管理に Issue Tracking System を利用しているのだが、(客先でのセキュリティポリシーなどの話もあり)外部のサービスは利用できません。
世間的には ITS としては Redmine が有力だと思うのですが、別途 DB が必要だったり機能が多すぎたりするので、最近勢いはないですが Python だけあれば利用できる Trac を使っています。
私が Ruby(on Rails) より Python が好きというのも大きいですが
いつもは、組み込みの Web サーバである tracd を利用しているのですが、 WSGI サーバである CherryPy を利用する場合についてまとめておきます。
プロジェクトをデプロイする
> trac-admin path\to\trac_project deploy path\to\deploy
上記のコマンドを実行すると、 path\to\deploy\cgi-bin\trac.wsgi
が生成されます。
trac.wsgi
を CherryPy から利用するために、 wsgi.py
にリネームしておきます。
CherryPy をインストールする
> pip install CherryPy
だけで OK です。
サーバスクリプトを作成する
以下のようにサーバスクリプトを作成しました。
- path\to\deploy\cgi-bin\server.py
# Import your application as: # from wsgi import application # Example: from wsgi import application # Import CherryPy import cherrypy if __name__ == '__main__': # Mount the application cherrypy.tree.graft(application, "/") # Unsubscribe the default server cherrypy.server.unsubscribe() # Instantiate a new server object server = cherrypy._cpserver.Server() # Configure the server object server.socket_host = "0.0.0.0" server.socket_port = 80 server.thread_pool = 30 # Subscribe this server server.subscribe() # Start the server engine (Option 1 *and* 2) cherrypy.engine.start() cherrypy.engine.block()
完全に公式のサンプルそのままです(笑)
CherryPy を利用したのは、更に Windows サービス化を行うための前フリだったりします。
Windows サービス化については後日まとめます。
binutils がクロスコンパイルできない→とりあえずバックトレースにコードの行数を出せるようになりました
先日組んだバックトレースのコードを拡張して、ソースコードの行数を表示するために libbfd
を組み込もうとした話。
#!/bin/bash export PATH=/path/to/buildroot/output/host/usr/bin:$PATH ./configure \ --build=$MACHTYPE \ --host=arm-buildroot-linux-uclibcgnueabi \ --target=arm-buildroot-linux-uclibcgnueabi \ --with-build-sysroot=/path/to/buildroot/output/target/lib \ make
上のようなスクリプトで make
すると、 gas
で mbstowcs
がない!というエラーになる。
binutils-2.25/gas/read.c:1642: undefined reference to `mbstowcs'
とはいえ、ほしいのは libbfd.a
と libiberty.a
、 libintl.a
だったので今回は解決を見送った。(必要なオブジェクトはコンパイル済みだったので)
最終的には、以下のようなコードでバックトレースが出力できるようになりました。
# makefile for backtrace. TARGET := backtrace ARCH := arm ifeq ($(ARCH),arm) CROSS_COMPILE := /path/to/buildroot/output/host/usr/bin/arm-buildroot-linux-uclibcgnueabi- else CROSS_COMPILE := endif CC := $(CROSS_COMPILE)gcc CFLAGS := -g -O0 -I. LDFLAGS := -rdynamic -ldl ifeq ($(ARCH),arm) CFLAGS += -DHAVE_CONFIG CFLAGS += -I/path/to/binutils-2.25/bfd CFLAGS += -I/path/to/binutils-2.25/include LDFLAGS += -mapcs-frame LDFLAGS += /path/to/binutils-2.25/bfd/libbfd.a LDFLAGS += /path/to/binutils-2.25/libiberty/libiberty.a LDFLAGS += /path/to/binutils-2.25/intl/libintl.a else LDFLAGS += -fno-omit-frame-pointer -lbfd endif all: $(CC) -std=c99 $(CFLAGS) -o $(TARGET) $(TARGET).c log.c $(LDFLAGS) clean: rm -rf $(TARGET)
/** @file backtrace.c * @brief porting backtrace. * @sa http://stackoverflow.com/questions/2536021/any-porting-available-of-stacktrace-for-uclibc/2536136#2536136 * @sa http://0xcc.net/blog/archives/000073.html */ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* for Dl_info */ #endif #if defined(HAVE_CONFIG) #include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <stdarg.h> #include <inttypes.h> #include <limits.h> #include <string.h> #include <dlfcn.h> #include <errno.h> #include <bfd.h> #include "log.h" extern void *__libc_stack_end; /** * Frame structure. */ #if defined(__x86_64) struct backtrace_frame { void *fp; void *lr; }; #elif defined(__ARM_ARCH_ISA_ARM) struct backtrace_frame { void *fp; void *sp; void *lr; void *pc; }; #endif static bfd *abfd; static asymbol **symbols; static int nsymbols; #define lengthof(array) (sizeof(array)/sizeof((array)[0])) __attribute__((constructor)) void init_bfd_stuff(void) { int size; abfd = bfd_openr("/proc/self/exe", NULL); if (abfd == NULL) { perror("bfd_openr"); exit(1); } bfd_check_format(abfd, bfd_object); size = bfd_get_symtab_upper_bound(abfd); if (size <= 0) { perror("fbd_get_symtab_upper_bound"); exit(2); } symbols = malloc(size); if (symbols == NULL) { perror("malloc"); exit(3); } nsymbols = bfd_canonicalize_symtab(abfd, symbols); } size_t Backtrace(void **frames, size_t size) { void *top_frame_p; void *current_frame_p; struct backtrace_frame *frame_p; size_t frame_count; top_frame_p = __builtin_frame_address(0); current_frame_p = top_frame_p; frame_count = 0; if ((current_frame_p != NULL) && (current_frame_p > (void *) &frame_count) && (current_frame_p < __libc_stack_end)) { while ((frame_count < size) && (current_frame_p != NULL) && (current_frame_p > (void *) &frame_count) && (current_frame_p < __libc_stack_end)) { #if defined(__x86_64) frame_p = (struct backtrace_frame *) (void **) current_frame_p; #elif defined(__ARM_ARCH_ISA_ARM) frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3); #endif frames[frame_count] = frame_p->lr; ++frame_count; current_frame_p = frame_p->fp; } } return frame_count; } void PrintBacktrace(void) { void *stack[128] = { NULL }, *address; Dl_info info; asection *section = bfd_get_section_by_name(abfd, ".debug_info"); const char *file_name; const char *func_name; int lineno; int found; int i; if (Backtrace(stack, lengthof(stack)) == 0) { fprintf(stderr, "not found backtrace...\n"); return; } for (i = 0; stack[i] != NULL; ++i) { address = stack[i]; if (section != NULL) { found = bfd_find_nearest_line(abfd, section, symbols, (long) address - 1, &file_name, &func_name, &lineno); } if (dladdr(address, &info) == 0) { fprintf(stderr, " unknown(?+?)[%p]\n", address); } else if (found == 0 || file_name == NULL) { fprintf(stderr, " %s(%s+%p)[%p]\n", info.dli_fname, info.dli_sname, address - info.dli_saddr, address); } else { fprintf(stderr, " %s(%s+%p)[%p] at %s:%d\n", info.dli_fname, info.dli_sname, address - info.dli_saddr, address, file_name, lineno); } } return; } int bar(void) { printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__); PrintBacktrace(); return 0; } int foo(void) { printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__); bar(); return 0; } int main(int argc, char **argv) { foo(); return 0; }
- strip なし(ARMV5)
# ./backtrace backtrace.c(163) foo: called backtrace.c(156) bar: called /root/backtrace(PrintBacktrace+0x4c)[0x12ce4] at /path/to/backtrace/backtrace.c:129 /root/backtrace(bar+0x24)[0x12eec] at /path/to/backtrace/backtrace.c:157 /root/backtrace(foo+0x24)[0x12f28] at /path/to/backtrace/backtrace.c:164 /root/backtrace(main+0x1c)[0x12f5c] at /path/to/backtrace/backtrace.c:170 /lib/libc.so.1(__uClibc_main+0x1fc)[0xb6f8972c]
- strip あり(X86_64)
backtrace.c(163) foo: called backtrace.c(156) bar: called ./backtrace(PrintBacktrace+0x54)[0x400ecd] ./backtrace(bar+0x27)[0x4010c6] ./backtrace(foo+0x27)[0x4010f4] ./backtrace(main+0x14)[0x40110f] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7f1e5e7ebec5]
このログ情報をハンドリングして良い方法で保存すればコンソールがデバッグログであふれることはなくなる、、、のかな?
Google 製の新しい圧縮アルゴリズム「 Brotli 」を使ってみた
最近は、組み込みでもCPU性能が上がっているため圧縮の要件も増えてきました。
zlibの組み込みがメインですが、他の圧縮方式も検討に加えられるか組み込みの観点で確認してみました。(圧縮率・時間の比較は他の記事に任せるとして)
brotli はGoogleがインターネット用に開発した圧縮アルゴリズムなので、比較的低負荷高圧縮な感じです。
ところが、いきなり圧縮アルゴリズムのほうが C++ で実装されていることが判明!
BSPやミドルウェアで圧縮機能は実装することが多いので、 C の実装であって欲しかった。。。(伸張アルゴリズムのほうは C 実装なのに。。。)
もう少し使ってみて、性能が良ければ圧縮アルゴリズムの C 実装も検討してみようかな。(あ、再実装しなくても、ライブラリ化して C のインタフェースを実装すればいいのか)
バックトレース in ARM(Qemu)
以前、挫折していた Linux 環境での C のバックトレース出力について、改めてトライしてみました。
ちなみに、ホスト環境(x86)ではそのものの backtrace()
や backtrace_symbols()
といったAPIが利用できるけど、 buildroot で構築したクロスビルド環境には execinfo.h
がなかったので、自力でバックトレースを取ることに。
業務で使用するログ機能の強化を考えていたのですが、スタックトレースは必須だと思っていたので、 Qemu 環境ではありますが動作して良かったです。
同じプログラムが業務で使用している開発ボードでも動作するか確認してみよう。
/** @file backtrace.c * @brief porting backtrace. * @sa http://stackoverflow.com/questions/2536021/any-porting-available-of-stacktrace-for-uclibc/2536136#2536136 */ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* for Dl_info */ #endif #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <stdarg.h> #include <inttypes.h> #include <limits.h> #include <dlfcn.h> #include <errno.h> #include "log.h" extern void *__libc_stack_end; struct backtrace_frame { void *fp; void *sp; void *lr; void *pc; }; #define lengthof(array) (sizeof(array)/sizeof((array)[0])) size_t Backtrace(void **frames, size_t size) { void *top_frame_p; void *current_frame_p; struct backtrace_frame *frame_p; size_t frame_count; top_frame_p = __builtin_frame_address(0); current_frame_p = top_frame_p; frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3); frame_count = 0; if ((current_frame_p != NULL) && (current_frame_p > (void *) &frame_count) && (current_frame_p < __libc_stack_end)) { while ((frame_count < size) && (current_frame_p != NULL) && (current_frame_p > (void *) &frame_count) && (current_frame_p < __libc_stack_end)) { frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3); frames[frame_count] = frame_p->lr; ++frame_count; current_frame_p = frame_p->fp; } } return frame_count; } void PrintBacktrace(void) { void *stack[128] = { NULL }; Dl_info info; int i; if (Backtrace(stack, lengthof(stack)) == 0) { fprintf(stderr, "not found backtrace...\n"); return; } for (i = 0; stack[i] != NULL; ++i) { if (dladdr(stack[i], &info) == 0) { fprintf(stderr, " unknown(???+???)[%p]\n", stack[i]); } else { fprintf(stderr, " %s(%s+%p)[%p]\n", info.dli_fname, info.dli_sname, stack[i] - info.dli_saddr, stack[i]); } } return; } int bar(void) { printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__); PrintBacktrace(); return 0; } int foo(void) { printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__); bar(); return 0; } int main(int argc, char **argv) { foo(); return 0; }
# makefile for backtrace. TARGET := backtrace ARCH := arm ifeq ($(ARCH),arm) CROSS_COMPILE := arm-buildroot-linux-uclibcgnueabi- else CROSS_COMPILE := endif CC := $(CROSS_COMPILE)gcc CFLAGS := -g -O0 -I. LDFLAGS := -rdynamic -ldl ifeq ($(ARCH),arm) LDFLAGS += -mapcs-frame else LDFLAGS += -fno-omit-frame-pointer endif all: $(CC) -std=c99 $(CFLAGS) -o $(TARGET) $(TARGET).c log.c $(LDFLAGS) clean: rm -rf $(TARGET)
出力結果は以下の通り。
# ./backtrace backtrace.c(101) foo: called backtrace.c(94) bar: called /root/backtrace(PrintBacktrace+0x2c)[0x88b4] /root/backtrace(bar+0x24)[0x898c] /root/backtrace(foo+0x24)[0x89c4] /root/backtrace(main+0x10)[0x8688] /lib/libc.so.1(__uClibc_main+0x1fc)[0xb6f0172c]
競技プログラミングはじめました
年末だけど、なにかあたらしいことを始めたいなぁとおもったので、以前から興味があった競技プログラムを始めてみました。
多くの競技プログラムサイトは英語圏のものなのですが、上記のAtCoderは完全日本語なので、「問題文が読めない!」という問題はないので安心して始められます(笑)
TopCoderなどの英語圏にもチャレンジしたいけど、まずは競技プログラムに慣れるところからということで。
競技プログラムはアルゴリズム要素が多くなるのだけれど、いままで勉強してこなかったので初歩の初歩から楽しんで取り組める感じ。
ケアレスミスもあったりして、改めて自分のプログラマーとしての実力もまだまだだということを実感しました。
他の人の回答も見られるので、学習効果は高いと思います。
仕事のプログラムは他人の解法と比べることはできませんからね。