Life, Education, Death

プログラミング以外でも思ったことをつらつらと書きたい

AppDelegateでStoryboardからNSWindowControllerを取得してウィンドウを表示するだけでハマった

XCode Version 7.1.1 (7B1005) Swift2.1で試しました。

かなりはまってしまったが、答えはここにあった。表示したいウィンドウのNSWindowControllerをメンバ変数等で拘束しておかないと正しく動作しなかった。 stackoverflow.com

最小コードにすると以下のようにshowWindowするときにインスタンス化されたコントローラーを束縛しておかないといけない。myControllerへの代入コードを除去すると表示されなくなってしまう。

var myController:NSWindowController? = nil

func applicationDidFinishLaunching(aNotification: NSNotification) {
    let controller = mainStoryboard.instantiateInitialController() as? NSWindowController
    myController = controller
    myController?.showWindow(nil)
}

CFArrayはArrayと等価じゃなかった

XCode Version 7.1.1 (7B1005) Swift2.1で試しました。

CGWindowListCreateDescriptionFromArrayに[Int]を渡したところコンパイルが通ったのでCFArrayとArrayが等価だと思ってていたが使い方が間違っていたようなのでメモ

let windowArray = [1310]
let windowsdescription:CFArrayRef = CGWindowListCreateDescriptionFromArray(windowArray)!;
let windowInfos = windowsdescription as NSArray? as? [[String: AnyObject]]
print(windowInfos?.description)

1310のWindowがあるときに上のようなコードだと、windowInfosが空っぽになってしまう。

let windowIds = [1310]
let windowArray = CFArrayCreate ( nil, UnsafeMutablePointer<UnsafePointer<Void>>(windowIds), windowIds.count,nil)
let windowsdescription = CGWindowListCreateDescriptionFromArray(windowArray)!;
let windowInfos = windowsdescription as NSArray? as? [[String: AnyObject]]
print(windowInfos?.description)

のようにCFArrayを作ってから関数を呼ぶようにすると無事適切な値が帰ってくるようになった。 それっぽくコンパイルが通っても意図通りになっていないケースがあるのでObjectiveCの関数を呼ぶ時は注意が必要そうだ。

GraphQLのサンプルに書き加えてみた

できたこと

独自のItemというデータ構造を追加してそれを作成・取得することができるようになった。

データ定義をしておくとバリテーションをしてくれることがわかった。 クライアント側で欲しいフィールドを調整したい場合は便利そうな気がした。

独自実装を追加していく

前回起動まで成功した https://github.com/RisingStack/graphql-serverをローカルにクローンしてきてこのクライアントとサーバーをいじっていきます。

Nodejs+MongoDBという環境で実装されていて、今回は

  1. mongoose(MongoDBライブラリ)のモデルクラスの定義
  2. GraphQLのスキーマを定義
  3. クライアント側コードを記述。作成と取得クエリを投げるようにする

という手順で作業を進めます。

mongoose(MongoDBライブラリ)のモデルクラスの定義

src/server/user.jsがmongooseのモデルクラスの定義なので、これをコピペしてsrc/server/item.jsを作ります。以下のようなコードになりました。

import mongoose from 'mongoose';

var ItemSchema = new mongoose.Schema({
  name: {
    type: String
  }
});

var Item = mongoose.model('Item', ItemSchema);

export default Item;
GraphQLのスキーマを定義

src/server/schema.jsを編集していきます。

import Item from './item';

を追加して先ほど書いたitem.jsを読み込みます。

GraphQLスキーマ向けにモデル定義をします。mongooseの方でもしてるので一緒にできると良いなと思います。

var itemType = new GraphQLObjectType({
  name: 'Item',
  description: 'Item creator',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The id of the item.',
    },
    name: {
      type: GraphQLString,
      description: 'The name of the item.',
    },
  })
});

GraphQLSchemaのqueryには取得クエリ、mutationには作成・更新・削除などの変更を加えるクエリを実装します。 詳しくは仕様を読むと良いです。

取得クエリ(getItem)を定義

  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      getItem: {
        type: itemType,
        args: {
          name: {
            name: 'name',
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: (root, {name}, source, fieldASTs) => {
          return Item.findOne({name: name});
        }
      },
    }
  }

次に作成クエリ(createItem)を定義します。

  mutation: new GraphQLObjectType({
    name: 'Mutation',
    fields: {
      createItem: {
        type: itemType,
        args: {
          name: {
            name: 'name',
            type: GraphQLString
          }
        },
        resolve: (obj, {name}, source, fieldASTs) => co(function *() {
          var item = new Item();
          item.name = name;

          return yield item.save();
        })
      },
    }
  }),

最終的にsrc/server/schema.jsは以下のようになりました。

import {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLSchema,
  GraphQLString,
  GraphQLList
} from 'graphql/type';

import co from 'co';
import User from './user';
import Item from './item';

/**
 * generate projection object for mongoose
 * @param  {Object} fieldASTs
 * @return {Project}
 */
function getProjection (fieldASTs) {
  return fieldASTs.selectionSet.selections.reduce((projections, selection) => {
    projections[selection.name.value] = 1;

    return projections;
  }, {});
}

var userType = new GraphQLObjectType({
  name: 'User',
  description: 'User creator',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The id of the user.',
    },
    name: {
      type: GraphQLString,
      description: 'The name of the user.',
    },
    friends: {
      type: new GraphQLList(userType),
      description: 'The friends of the user, or an empty list if they have none.',
      resolve: (user, params, source, fieldASTs) => {
        var projections = getProjection(fieldASTs);
        return User.find({
          _id: {
            // to make it easily testable
            $in: user.friends.map((id) => id.toString())
          }
        }, projections);
      },
    }
  })
});

var itemType = new GraphQLObjectType({
  name: 'Item',
  description: 'Item creator',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The id of the item.',
    },
    name: {
      type: GraphQLString,
      description: 'The name of the item.',
    },
  })
});

var schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: function() {
          return 'world';
        }
      },
      user: {
        type: userType,
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: (root, {id}, source, fieldASTs) => {
          var projections = getProjection(fieldASTs);
          return User.findById(id, projections);
        }
      },

      getItem: {
        type: itemType,
        args: {
          name: {
            name: 'name',
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: (root, {name}, source, fieldASTs) => {
          return Item.findOne({name: name});
        }
      },
   }
  }),

  // mutation
  mutation: new GraphQLObjectType({
    name: 'Mutation',
    fields: {
      createUser: {
        type: userType,
        args: {
          name: {
            name: 'name',
            type: GraphQLString
          }
        },
        resolve: (obj, {name}, source, fieldASTs) => co(function *() {
          var projections = getProjection(fieldASTs);

          var user = new User();
          user.name = name;


          return yield user.save();
        })
      },
      deleteUser: {
        type: userType,
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: (obj, {id}, source, fieldASTs) => co(function *() {
          var projections = getProjection(fieldASTs);
          console.log(id);
          return yield User.findOneAndRemove({_id: id});
        })
      },
      updateUser: {
        type: userType,
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLString)
          },
          name: {
            name: 'name',
            type: GraphQLString
          }
        },
        resolve: (obj, {id, name}, source, fieldASTs) => co(function *() {
          var projections = getProjection(fieldASTs);

          yield User.update({
            _id: id
          }, {
            $set: {
              name: name
            }
          });

          return yield User.findById(id, projections);
        })
      },

      createItem: {
        type: itemType,
        args: {
          name: {
            name: 'name',
            type: GraphQLString
          }
        },
        resolve: (obj, {name}, source, fieldASTs) => co(function *() {
          var projections = getProjection(fieldASTs);

          var item = new Item();
          item.name = name;

          return yield item.save();
        })
      },
    }
  }),
});

export var getProjection;
export default schema;
クライアント側コードを記述。作成と取得クエリを投げるようにする

作成クエリを投げるコードを書くのでsrc/client/mutation.jsを編集します。

Itemデータを作成するために先ほど実装したcreateItemを使います。hogehogehogeというItemを作成します。

var itemName = 'hogehogehoge'

request
  .post('http://localhost:3000/data')
  .send({
    query: `
    mutation M($name: String!) {
      createItem(name: $name) {
        name
      }
    }
    `,
    params: {
      name: itemName
    }
  })
  .end(function (err, res) {
    debug(err || res.body);
    debug('created', res.body);
  });

src/client/mutation.jsは以下のようになりました。最初からあったコードはデバッグの都合で除去しました。

import request from 'superagent';
import Debug from 'debug';

var debug = new Debug('client:mutation');
var itemName = 'hogehogehoge'

request
  .post('http://localhost:3000/data')
  .send({
    query: `
    mutation M($name: String!) {
      createItem(name: $name) {
        name
      }
    }
    `,
    params: {
      name: itemName
    }
  })
  .end(function (err, res) {
    debug(err || res.body);
    debug('created', res.body);
  });

取得クエリを投げるコードを書きます。src/client/query.jsを以下のようなコードを追加します。 (var name = 'hogehogehoge'としていたらうまく動かなかったです。変数名とフィールド名がぶつかるとまずい?)

var itemName = 'hogehogehoge'
request
  .get('http://localhost:3000/data')
  .query({
    query: `{
      getItem(name: "${itemName}") {
        name
      }
    }`
  })
  .end(function (err, res) {
    debug('items', res.body);
  });

ということで、src/client/query.jsは以下のようになりました。最初からあったコードはデバッグの都合で除去しました。

import request from 'superagent';
import Debug from 'debug';

var debug = new Debug('client:query');
var itemName = 'hogehogehoge'

request
  .get('http://localhost:3000/data')
  .query({
    query: `{
      getItem(name: "${itemName}") {
        name
      }
    }`
  })
  .end(function (err, res) {
    debug('items', res.body);
  });

上記のようにコードを編集していったら、前回同様に

npm start

としてサーバーを起動して、別のscreenで

npm run client

とクライアントを起動すると、無事データの作成と取得が確認できます。

GraphQLサーバー起動まで試した

Facebookの発表したGraphQLが気になったのでインストールまでやってみました。

qiitaではまだ記事が全然上がっていない模様。

GraphQLに関する1件の投稿 - Qiita

ここにすでにサーバーの実装がいたので試すのは簡単そう。

RisingStack/graphql-server · GitHub

インストールの説明に手順が書いてあるので問題ないのですが以下のような手順でやりました。 環境はUbuntu 14.04です。

sudo apt-get install mongodb
git clone https://github.com/RisingStack/graphql-server.git ~/graphql-server
cd ~/graphql-server
npm install
npm run seed
npm start

これでサーバーが起動します。mongodbが起動していないと、"npm run seed"のところで

Error: connect ECONNREFUSED
    at exports._errnoException (util.js:746:11)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1010:19)

こんなエラーが出てしまうので注意。

無事サーバーが起動したところで、別のコンソールで

npm run client

とクライアントを起動すると何か出力が出て起動を確認できました。めでたしめでたし

次はもう少し弄ってみる予定。

普段どうやってエラーメッセージから問題を解決しているか?

そもそも

慣れれば簡単にぐぐれるだろ!って思いがちなんだけど、普段どうしているかちゃんと振り返っておかないと、人に伝えられないので振り返ってみます。

エラーに遭遇する

エラーは突然やってくる。ちょっと今流行りのモジュールを動かそうと思ってwikiやブログ記事の通りにコマンドを打ったのにも関わらずハマってしまうことがよくある。

今回は、bundle installをして必要なライブラリをセットアップしようと思ったらハマってしまったあんまりプログラミングが詳しくない人を想定しています。

bundle install

Fetching gem metadata from http://rubygems.org/...........
Fetching version metadata from http://rubygems.org/...
Fetching dependency metadata from http://rubygems.org/..
Resolving dependencies....
Installing rake 10.4.2
Installing addressable 2.3.8
Installing io-like 0.3.0
Installing archive-zip 0.7.0
Installing backports 3.6.4
Using bundler 1.10.3
Installing coderay 1.1.0
Installing daemons 1.2.2
with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    /Users/hogehoge/.rvm/rubies/ruby-2.0.0-p0/bin/ruby -r ./siteconf20150607-53401-6yipdt.rb extconf.rb
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... yes
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... no
checking for gethrtime()... no
creating Makefile

make "DESTDIR=" clean

make "DESTDIR="
compiling binder.cpp
make: g++-4.2: No such file or directory
make: *** [binder.o] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/hogehoge/.rvm/gems/ruby-2.0.0-p0/gems/eventmachine-1.0.7 for inspection.
Results logged to /Users/hogehoge/.rvm/gems/ruby-2.0.0-p0/extensions/x86_64-darwin-11/2.0.0/eventmachine-1.0.7/gem_make.out
An error occurred while installing eventmachine (1.0.7), and Bundler cannot
continue.
Make sure that `gem install eventmachine -v '1.0.7'` succeeds before bundling.

時間が無駄にかかるぅって気分になるんですが、冷静になって対処します。

まずはErrorって文字がある行を探します。

ターミナルの設定によっては赤く強調されているかもしれませんね。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
make: *** [binder.o] Error 1
An error occurred while installing eventmachine (1.0.7), and Bundler cannot
continue.

の3箇所が見つかります。とりあえず

  • gem native extensionってのがビルドエラーになった
  • よくわからないけど、なんかエラーが起きた
  • eventmachine (1.0.7)がインストールできなくてBundlerが中断された ことがわかりました。

ある程度rubyを知っていればスキップできますが、知らない場合は文章に含まれるキーワードっぽいものを探してぐぐります。 今は、rubyをいじっていてBundlerを使ってインストールをしようとしているので、

ruby eventmachine

などとぐぐって、これが何なのか概要を確認しておくと良い場合があります。

一旦、eventmachineというライブラリのインストールに失敗してしまったことがわかりました。 (native extensionとは何なのか不明ですが)

キーワードを適当に並べてぐぐってみて、同じエラーが出て困っていた人がいないか調べます。 今回はこんなキーワードにしました。

gem eventmachine native extension error

ぐぐったところ、検索結果1ページ分全部見て同じシチュエーションの記事が見つけられなかったので最初のエラー内容をもう少し見直します。

他の作戦を考える

だいたいエラーメッセージの前後に、原因の詳細が出ていることが多いので周辺を確認します。

make "DESTDIR=" clean

make "DESTDIR="
compiling binder.cpp
make: g++-4.2: No such file or directory
make: *** [binder.o] Error 1

make failed, exit code 2

が気になりました。makeとはなんでしょうか?とりあえずぐぐります。 たぶんMake: Japanではないでしょうw

wikipediaが見つかりました。その他の数ページを軽くみた限りではコマンド名であることがわかります。make - Wikipedia

普段コンパイルするような言語を触っていないとwikipediaに出てくる数々にキーワードが全然ピンと来なくて沼にハマりそうな気がしますが、あまり気にせず無視して進みましょう。

makeがコマンド名ということがわかりました。先ほどのmakeコマンドから出ているであろうメッセージは若干の英語力があれば、短い文章なので理解ができると思います。

make: g++-4.2: No such file or directory

何かファイルかフォルダがないようです。これはエラーの原因の可能性がありそうなメッセージですね。 g++4.2という新しいキーワードがまた出てきましたが、一旦無視してぐぐります。

make g++-4.2: No such file or directory

すると

http://blog.inouetakuya.info/entry/20120716/1342415386

というページを見つけました。インストールに失敗しているライブラリは違いますが同じmakeコマンドからのメッセージが出ているので試してみます。 先ほどのブログを上から読んで

gcc と g++ がインストールされているか確認

うん、確かにg++というのはあるぞ。

gcc 及び g++ へのシンボリックリンクを作成する

適宜シンボリックリンクが何か調べた方がいいですが、最悪とりあえずコピペでやってみるのもアリ。

g++4.2がないと怒られているので、これだけやれば良さそうです。

sudo ln -s /usr/bin/g++ /usr/bin/g++-4.2

改めてbundle installをしてみたところインストールが完了しました。めでたしめでたし

まとめ

この前ハマったのをモデルケースとして出来るだけ知識がないつもりで再度やってみました。

以下の点がポイントかなと個人的には思っています。

  • とりあえずエラーメッセージとそうでないものを仕分ける
  • エラーメッセージ周辺を読む(簡単な英語力で読めるケースが多い)
  • 概要だけを把握して、どういうキーワードでぐぐるべきかの判断力を磨く
  • とりあえずググる。だいたい個人ブログかstack overflowに回答がある

最初のうちは周りに詳しい人がいれば、サクッと聞いてしまうのが良いですね。これはこれで良い聞き方の話がありそうなので気が向いたら整理したいところです。

QNapにgitbucketをインストール

ググるとちょこちょこ試している人がいるようなので割と簡単にインストールできるみたい。実際に試してみた。

  1. AppCenterからJRE_ARM ver8.6.0、Tomcat ver7.0.50、git ver1.8.4.2-1をインストール
  2. Tomcatのページを開いて、gitbucketのwarファイルをデプロイ
    gitbucketの最新版はこちら

という手順で簡単インストールの予定だったが、いくつかハマってしまった。

ハマったところ

Tomcatを起動したら白い画面

インストールできたものの起動したら白い画面であの可愛くない猫が表示されなかった。
よく原因がわかってないが、TomcatのサービスをAppCenterから一度無効にして再度有効化したら治った。

ファイルサイズ制限に引っかかった

gitbucketをデプロイしようとしたところデプロイできるファイルサイズ制限に引っかかったのでsshnasに入って

/share/MD0_DATA/.qpkg/Tomcat/tomcat/webapps/manager/WEB-INF/web.xml
vimで以下のように編集してtomcatを再起動してやり直しました。

    <multipart-config>
      <!-- 60MB max -->
      <max-file-size>62914560</max-file-size>
      <max-request-size>62914560</max-request-size>
      <file-size-threshold>0</file-size-threshold>
    </multipart-config>

なんか起動しなくなった

gitbucketの設定ファイル は
/share/MD0_DATA/homes/admin/.gitbucket/gitbucket.conf
にあったのでインストールをやり直すときはこれをフォルダごと消してやり直してどうにかなった。

pogoplugにdebianインストールできました

手元に結局使わず転がっていたPogoplug Mobileを有効利用すべくDebianをインストールしました。2014/11/22時点では簡単にインストールができました

http://blog.qnology.com/2014/07/hacking-pogoplug-v4-series-4-and-mobile.html

の手順の通りに上から順にコマンドを打っていくだけです。
DebianをSDカードインストールすることを前提にしているのでUSBメモリからOSを起動したい場合はところどころコメントでUSBメモリの場合はこちらみたいに書かれているので確認しながらコマンドを打つと良いです。

root@debian:~# cat /etc/debian_version
7.4

 のようにDebian 7.4がインストールできました。eyefiのサーバーとか音楽サーバーに出来ないか色々試していきたいです。