Quantcast
Channel: 気ままなタンス*プログラミングなどのノートブック
Viewing all 242 articles
Browse latest View live

【Windows10】[オススメ]スタートメニューを使いやすくするための工夫

$
0
0

Windows10スタートメニューの不満点

Windows10を利用していて、使いづらいと思うことがあります。

それは、スタートメニューに、「マイコンピュータ」(各ドライブのルート)への移動方法がないことです。

僕はいつもスタートメニューから、「マイコンピュータ」を経由して、 各フォルダに移動していたので、そのルートが断ち切られてしまい少々不便に感じていました。

ちょっとした工夫

そこで一つ工夫をすることにしました。

単純ではありますが、 スタートメニューの上部に「マイコンピュータ」に対応する「PC」タイルを配置するというものです。

  • スタートメニューを開いたら、すぐに「PC」がある

こうすることで、従来までの「スタートメニュー」と同じような操作で各ドライブのルートに移動することができます。

f:id:rinne_grid2_1:20160418093353p:plain

やりかた

1. 左下の検索(WebとWindowsを検索)より、「PC」を検索

f:id:rinne_grid2_1:20160418093819p:plain

2. 「PC」が見つかったら右クリックをして「スタート画面にピン留めする」を選択

f:id:rinne_grid2_1:20160418093912p:plain

3. 一旦スタートメニューを閉じて、再度開き、「タイル」がある部分を下にスクロールし「PC」を見つける

f:id:rinne_grid2_1:20160418094024p:plain

4. 「PC」を上部にドラッグドロップし、グループに名前をつける

f:id:rinne_grid2_1:20160418094057p:plain

5. これで完成

f:id:rinne_grid2_1:20160418094116p:plain

僕のPCはこんな感じにしています。

PC、マイドキュメント、コントロールパネルを追加

f:id:rinne_grid2_1:20160418094920p:plain

その他

なお、「スタートメニューからじゃなくて、エクスプローラ開くときにクイックアクセスじゃなくてPCに移動したい」という場合は、 以下の記事の迷った点と備忘録を参照すると、やり方が書いてあります。

http://rinnegrid.hatenablog.com/entry/2015/08/17/071745rinnegrid.hatenablog.com

検索バーで入力すればすぐに出てくるのですが、 毎回入力するのはわりと手間ですし、やはり慣れている操作方法でできるのがベストだと思います。


【RPGツクールMV】[中級者向け]セーブ・ロードで利用されているプログラムの調査記録

$
0
0

回想モードプラグインで、セーブデータ間のスイッチ情報を共有する機能の実装を検討していました。

その中で、セーブ・ロード時に利用するメソッドやゲームオブジェクトの変化といった詳細な情報が必要となり、調査したので記録に残します。

loadFromLocalFile(ローカル実行時のセーブファイル読み込み)

// rpg_managers.js 610行目
StorageManager.loadFromLocalFile = function(savefileId) {var data = null;
    var fs = require('fs');
    var filePath = this.localFilePath(savefileId);
    if (fs.existsSync(filePath)) {
        data = fs.readFileSync(filePath, { encoding: 'utf8'});
    }return LZString.decompressFromBase64(data);
};
  • this.localFilePathは、以下のような条件でパスを返す
    • savefileIdが0より小さい場合:"config.rpgsave"を返す
    • savefileIdが0と同じ場合:"global.rpgsave"を返す
    • savefileIdが1以上の場合:"file%1.rpgsave".format(savefileId)を返す

loadFromWebStorage(ブラウザ実行時のセーブファイル読み込み)

// rpg_managers.js 639行目
StorageManager.loadFromWebStorage = function(savefileId) {var key = this.webStorageKey(savefileId);
    var data = localStorage.getItem(key);
    return LZString.decompressFromBase64(data);
};
  • this.webStorageKeyは以下のような条件でキー名を返す
    • savefileIdが0より小さい場合:"RPG Config"
    • savefileIdが0と同じ場合:"RPG Global"
    • savefileIdが1以上の場合:"RPG File%1".format(savefileId);

saveToLocalFile(ローカル実行時のセーブファイル書き込み)

// rpg_managers.js 599行目
StorageManager.saveToLocalFile = function(savefileId, json) {var data = LZString.compressToBase64(json);
    var fs = require('fs');
    var dirPath = this.localFileDirectoryPath();
    var filePath = this.localFilePath(savefileId);
    if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath);
    }
    fs.writeFileSync(filePath, data);
};
  • 引数で受け取ったjsonをBase64にエンコード

saveToWebStorage(ブラウザ実行時のセーブファイル書き込み)

// rpg_managers.js 633行目
StorageManager.saveToWebStorage = function(savefileId, json) {var key = this.webStorageKey(savefileId);
    var data = LZString.compressToBase64(json);
    localStorage.setItem(key, data);
};

ローカルか、ブラウザ実行かの判定

非常にわかりやすいメソッド名ですね。

// rpg_managers.js 595行目
StorageManager.isLocalMode = function() {return Utils.isNwjs();
};

セーブ(ローカル、ブラウザ)の判定

// rpg_managers.js 563行目
StorageManager.save = function(savefileId, json) {if (this.isLocalMode()) {this.saveToLocalFile(savefileId, json);
    }else{this.saveToWebStorage(savefileId, json);
    }};

セーブの呼び出し

json文字列の長さが200,000以上だった場合はワーニングを出力するようですね。ただ、その条件に一致する場合に変数操作は行っておらず、エラー等にはならないようです。

// rpg_managers.js 362行目
DataManager.saveGameWithoutRescue = function(savefileId) {var json = JsonEx.stringify(this.makeSaveContents());
    if (json.length >= 200000) {
        console.warn('Save data too big!');
    }
    StorageManager.save(savefileId, json);
    this._lastAccessedId = savefileId;
    var globalInfo = this.loadGlobalInfo() || [];
    globalInfo[savefileId] = this.makeSavefileInfo();
    this.saveGlobalInfo(globalInfo);
    returntrue;
};
  • this.makeSaveContentsで、ゲーム変数($gameSystem等)をオブジェクトに格納
// rpg_managers.js 422行目
DataManager.makeSaveContents = function() {// A save data does not contain $gameTemp, $gameMessage, and $gameTroop.var contents = {};
    contents.system       = $gameSystem;
    contents.screen       = $gameScreen;
    contents.timer        = $gameTimer;
    contents.switches     = $gameSwitches;
    contents.variables    = $gameVariables;
    contents.selfSwitches = $gameSelfSwitches;
    contents.actors       = $gameActors;
    contents.party        = $gameParty;
    contents.map          = $gameMap;
    contents.player       = $gamePlayer;
    return contents;
};
  • ゲーム変数を格納したオブジェクトをjsonに変換し、563行目のsaveメソッドに渡す
  • makeSavefileInfoメソッドで、セーブ画面に表示するキャラクターやプレイ時間等の情報をオブジェクトにセットする
// game_managers.js 411行目
DataManager.makeSavefileInfo = function() {var info = {};
    info.globalId   = this._globalId;
    info.title      = $dataSystem.gameTitle;
    info.characters = $gameParty.charactersForSavefile();
    info.faces      = $gameParty.facesForSavefile();
    info.playtime   = $gameSystem.playtimeText();
    info.timestamp  = Date.now();
    return info;
};

ロード(ローカル、ブラウザ)の判定

StorageManager.load = function(savefileId) {if (this.isLocalMode()) {returnthis.loadFromLocalFile(savefileId);
    }else{returnthis.loadFromWebStorage(savefileId);
    }};

ロードの呼び出し

DataManager.loadGameWithoutRescue = function(savefileId) {var globalInfo = this.loadGlobalInfo();
    if (this.isThisGameFile(savefileId)) {var json = StorageManager.load(savefileId);
        this.createGameObjects();
        this.extractSaveContents(JsonEx.parse(json));
        this._lastAccessedId = savefileId;
        returntrue;
    }else{returnfalse;
    }};
  • StorageManager.loadで、セーブファイルから文字列(Base64からデコード済み)を取得
  • this.createGameObjectsで、Game_XXXXオブジェクトを生成
// game_managers.js 194行目
DataManager.createGameObjects = function() {
    $gameTemp          = new Game_Temp();
    $gameSystem        = new Game_System();
    $gameScreen        = new Game_Screen();
    $gameTimer         = new Game_Timer();
    $gameMessage       = new Game_Message();
    $gameSwitches      = new Game_Switches();
    $gameVariables     = new Game_Variables();
    $gameSelfSwitches  = new Game_SelfSwitches();
    $gameActors        = new Game_Actors();
    $gameParty         = new Game_Party();
    $gameTroop         = new Game_Troop();
    $gameMap           = new Game_Map();
    $gamePlayer        = new Game_Player();
};
  • セーブファイルの文字列を、jsonパースし、ゲーム変数($gameSystem等)に格納
// game_managers.js 438行目
DataManager.extractSaveContents = function(contents) {
    $gameSystem        = contents.system;
    $gameScreen        = contents.screen;
    $gameTimer         = contents.timer;
    $gameSwitches      = contents.switches;
    $gameVariables     = contents.variables;
    $gameSelfSwitches  = contents.selfSwitches;
    $gameActors        = contents.actors;
    $gameParty         = contents.party;
    $gameMap           = contents.map;
    $gamePlayer        = contents.player;
};

それでは、皆様も楽しいツクールライフを!

【雑記】[自己分析]なにがやりたいか聞かれて言葉に詰まった話

$
0
0

なにがやりたいか

懇親会で上司や先輩と飲んでいた時、質問されました。

「よく家でコーディングやってるみたいだけど、やりたいことは何?」

その質問を受けて、少々困惑してしまいました。 なぜならば、自分自身では何がやりたいのか、明確にできているつもりだったのですが、 客観的に見ると、明確に意識できていないことに気づいてしまったのです。

根底にある考え

基本的にはプラグインの開発やフリーゲームの作成を行い、 「人の役に立つことをしたい、人を楽しんでもらいたい」という想いの元に動いています。

仕事でも他の人から「これ作って」「これやって」と言われたら、 その人のことを優先的に考えて行動しています。*1

(どうやったら使いやすいか常に考えたり、はやく使わせてあげたいと常々考えている気がします)

しかし、人の役に立つことをしたいというのは、 先輩からしたら、曖昧な考えであったようでした。 今振り返ると確かにその指摘も妥当だと感じました。

指摘を受けて振り返る

なんとなくではあったのですが、 自分から主体的にモノを考え、何かに取り組むことができておらず、悩み続けていました。

何かを作ろうとしても、 「既存のサービスがあるから作っても無駄」「1から作るにも、数式が入ってくると、膨大な時間がかかりそう」 こんな理由で反射的・無意識的に断念していることがわかりました。

新しいプログラミング言語を学ぶにしても、そこからどんなものを生み出すか見当がつかない状況といえば良いのでしょうか。 (仕事で新言語での開発等に携わっていれば、ノウハウ蓄積の観点で違ったものになったのかもしれません)

本当に先輩が聞きたかったことは何か

会話にズレがあったようにも感じます。

先輩としては、BtoBか、BtoCかという、金銭の絡む話の中で「やりたいこと」が聞きたかった様子でした。

しかし、現在の自分としては報酬等、かかわりのない部分で動いています(が、やりたいことは明確に言えません)*2

今後自分はなにをしていくべきか

未完成の状態にあるものを挙げてみることにします

  • ツクールMVゲーム
  • ツクールVX aceゲーム
  • プラグイン(RecollectionMode)の機能追加

最近はツクールMVも影響で、JavaScriptを学ぶことが多くなってきました。 (ツクールMVJavaScript、Node.jsやES2015)

クライアント側で完結するものだけではなくWebアプリも作りたいと考えています(主にDjango。だけどネタがないのです・・・)

少し整理すると、「やりたいことは盛りだくさんな状況」であるが、やりたいことが多すぎて、結局何にも取り組めていない状況と言えそうです。

質問に即答できなかったことを後悔しつつ、自己分析を実施するきっかけとなったので、結果オーライですね。

*1:もちろん自分の仕事が忙しいときは少々ずれることはあります

*2:というよりも、今は言えないだけで作りたいものがでてきたら当然それが最優先になるので、一時的なものだとも思うのです

【RPGツクールMV】マップ上に表示するウィンドウに関するコードリーディングのメモ

$
0
0

こんにちは!

RPGツクールMVで、マップが切り替わった時にそのマップの名前を表示するウィンドウがありますよね(下記イメージの赤枠)。 f:id:rinne_grid2_1:20180509213711p:plain

このウィンドウはマップに入った時にフェードイン・アウトしながら表示されます。 RPGツクールMVでは、マップの「表示名」に入れるだけで、このマップ名称を表示するウィンドウが有効になるんです!*1

フェードイン・アウトしながらウィンドウを表示する機能は、マップ以外にも、ミニゲーム等のいろんな部分に使えそうです。

そこで、どのようなソースコードでこの仕組みを実現しているのかを調べてみることにしました。

参考とする関数オブジェクト

マップ上で呼び出されることと、マップ名を表示することから、 下記のファイル・関数オブジェクトに当たりをつけて調べました。

  • rpg_scenes.js - Scene_Map
  • rpg_windows.js - Window_MapName

ざっくりとした流れ

  • マップオブジェクトの作成
  • マップ準備完了
  • マップロードイベント発火

マップに必要なウィンドウの準備(Scene_Map.prototype.createDisplayObjects)

まず、マップ用のシーンで定義されたcreateDisplayObjectsで、 ウィンドウレイヤやスプライトセット、さらに今回の対象であるMapNameWindowを作成するメソッドを呼び出します

Scene_Map.prototype.createDisplayObjects = function() {this.createSpriteset();
    this.createMapNameWindow();
    this.createWindowLayer();
    this.createAllWindows();
};

マップ表示名のウィンドウ作成(Scene_Map.prototype.createMapNameWindow)

  • createMapNameWindowを呼び出すことによって、this._mapNameWindowにオブジェクトが作成されます
  • また、作成したオブジェクトをシーンの子オブジェクトとして追加します
Scene_Map.prototype.createMapNameWindow = function() {this._mapNameWindow = new Window_MapName();
    this.addChild(this._mapNameWindow);
};

マップ名を表示するウィンドウの初期化(Window_MapName.prototype.initialize)

上記createMapNameWindowの中で、Window_MapName()がnewされた時点でinitializeが呼び出されます

Window_MapName.prototype.initialize = function() {var width = this.windowWidth();
    var height = this.windowHeight();
    Window_Base.prototype.initialize.call(this, 0, 0, width, height);
    this.opacity = 0;
    this.contentsOpacity = 0;
    this._showCount = 0;
    this.refresh();
};

initializeでは、下記のような初期化を行っているようです。

  • width, heightの設定
  • ウィンドウ自体の透明度の設定
  • コンテンツの透明度の設定
  • フェードインにかける時間の指定
  • ウィンドウ内容のリフレッシュ

マップ名を表示するウィンドウ内容の表示(Window_MapName.prototype.refresh)

Window_MapName.prototype.refresh = function() {this.contents.clear();
    if ($gameMap.displayName()) {var width = this.contentsWidth();
        this.drawBackground(0, 0, width, this.lineHeight());
        this.drawText($gameMap.displayName(), 0, 0, width, 'center');
    }};

次に、リフレッシュが呼び出され、 一度コンテンツ(描画領域等)をクリアするようです。

その後、もし表示名$gameMap.displayNameが指定されている場合は、背景やマップ名の表示をしています。

ウィンドウの背景を設定(Window_MapName.prototype.drawBackground)

Window_MapName.prototype.drawBackground = function(x, y, width, height) {var color1 = this.dimColor1();
    var color2 = this.dimColor2();
    this.contents.gradientFillRect(x, y, width / 2, height, color2, color1);
    this.contents.gradientFillRect(x + width / 2, y, width / 2, height, color1, color2);
};

マップ名表示の領域のグラデーションは、gradientFillRectで表現されているようです。

マップ名表示ウィンドウをオープン(Scene_Map.prototype.start)

さらに処理を追うと、 マップのシーンが開始する際に、this._mapNameWindow.open()でマップ名表示ウィンドウを開く処理が呼び出されることがわかります。

Scene_Map.prototype.start = function() {
    Scene_Base.prototype.start.call(this);
    SceneManager.clearStack();
    if (this._transfer) {this.fadeInForTransfer();
        this._mapNameWindow.open();
        $gameMap.autoplay();
    }elseif (this.needsFadeIn()) {this.startFadeIn(this.fadeSpeed(), false);
    }this.menuCalling = false;
};

ウィンドウオープンをフェードインで表現するための準備(Window_MapName.prototype.open)

Window_MapName.prototype.open = function() {this.refresh();
    this._showCount = 150;
};

リフレッシュ処理を呼び出し、showCountに150という値をセットしています。 この値がフェードイン・アウトを行う際に増減されるようです。

共通のupdateイベントによる、Windowのアップデート

Window_MapName.prototype.update = function() {
    Window_Base.prototype.update.call(this);
    if (this._showCount > 0 && $gameMap.isNameDisplayEnabled()) {this.updateFadeIn();
        this._showCount--;
    }else{this.updateFadeOut();
    }};
Window_MapName.prototype.updateFadeIn = function() {this.contentsOpacity += 16;
};

Window_MapName.prototype.updateFadeOut = function() {this.contentsOpacity -= 16;
};
  • contentOpacityというのは、名前から察するに透明度のことのようです
    • updateFadeIn()では、contentsOpacity の値を16増やしています
    • updateFadeOutでは、contentsOpacity の値を16減らしています
  • updateは、1フレームごとに呼び出されるので、フレーム毎に不透明度を16増減しているようです。
  • showCountが0より大きい場合はフェードイン、それ以外の場合はフェードアウトを呼び出します。
    • 60フレーム = 1秒 がツクールMVの基本なので、150フレーム=2.5秒でフェードイン・アウトを実行しているようです。
    • ただし、不透明度255が最大であると考えると、150フレーム丁度で表示されるわけではなく、だいたい16フレーム(255/16)くらいで完全に表示され、残り134フレーム+αを表示した後、16フレームで完全に非表示となってしまうことが考えられます。

まとめ

コードリーディングを行うことによって、下記のようなことがわかりました。

  • showCount = フレーム数(1/60秒)で表示時間を制御している
  • フェードイン・フェードアウトは不透明度を増減することによって実現している

*1:RPGツクールVXから実装された素敵な機能です

【RPGツクールMV】これで解決!スマホブラウザでInvalidStateErrorが発生した場合の対応記録

$
0
0

概要

RPGツクールMVで、ウェブブラウザ向けにデプロイメントを実施し、 iPhoneやiPad等のiOS系端末でゲームをプレイしていると下記のようなエラーが発生しました。

Error
InvalidStateError (DOM Exception 11): The object is in an invalid state.

原因

ゲームイベントで指定されている BGMやSEのm4a形式ファイルが存在しないためです。

対策

m4a形式ファイルをインポートする必要があります。

気を付けた方が良いこと

PC版のブラウザだと、開発者コンソールに「ファイルが見つからない」エラー出力があり、ゲームの継続が可能です。 しかしスマホ・タブレットの場合はゲーム自体が停止してしまいます。

個人的な話なのですが、外部サイト様の音楽素材を利用する際、 oggファイルだけをインポートし、m4aファイルの方が漏れてしまうことがたまにあるので、 それぞれのファイルをセットでインポートすることを忘れないようにしないといけませんね。

m4aファイルを配置しても、上記エラーが発生した場合

  • 下記の端末で、現象を確認
    • iPod touch(MKH02J/A iOS 10.1.1)のSafari、Chrome
    • iPad mini2(ME280J/A iOS 10.0.2)のSafari、Chrome

推測と対策

  • m4aファイルのサイズが大きすぎる可能性があります。
    • 2種類のm4aファイル(1.9MB、2.4MB)がそれぞれ再生されませんでした
    • ビットレート変更ツール等を利用し、音質を下げ、1.1MB程度に圧縮したところ、問題なく再生できました
    • ※ブラウザキャッシュを削除しないと、キャッシュからBGMを読んでしまい、いつまでたっても反映されないようです

公開バージョンで、ファイル系エラーを起こさないために

本事象に出くわしたことで、公開時に留意しておくべき点がいくつかわかりました

  • ゲーム停止は絶対に避ける(ゲーム進行できないより、音楽がならない方がマシ)
  • m4aファイルサイズを意識する

ゲーム停止を避けるために

とても素敵なプラグインがありました。

それでは、皆様も楽しいツクールライフを!

【2018年5月最新】【体験レポート】[審査に落ち続けてる人必見]Google Adsenceの審査に7回目でようやく通過したので、対応内容を時系列で詳しく書きます

$
0
0

こんばんは。長かったゴールデンウィークはあっという間に過ぎ去ってしまいました!

GW最終日の昨日、ようやくGoogle Adsence審査を通過することができました。

そして、申請回数はタイトル通り・・・計7回です!

申請前にGoogle Adsenceについて調査していた際、 「一発OKだった」や、「ほんの数時間で通過連絡が来た」という形で書いてあったブログが多く出てきたので、 「なんとか大丈夫だろう」と、少々甘く考えていたのですが、現実は厳しかったです。

そして何より、1回目の申請で通過する方々は、凄すぎる・・・!

「何で自分のブログは駄目なのだろうか」と、計6回も落ち込んだのですが、最終的には審査通過し、通過メールと一緒にやってきたポップコーンの彼(黄色背景の画像)と対面することができました。

f:id:rinne_grid2_1:20180508010549p:plain

(通過メールには、上記画像がgif形式で貼付されているので、gifアニメで動いている姿を見てびっくりしました)

さて、今日の記事の本題です。 Google Adsenceに関して、6回も不承認連絡が来てしまい、とてもスマートとは言えず、非常に泥臭い感じがありますが・・・ 1回目の申請から通過まで間で、必死に試行錯誤を繰り返した内容を記事としてまとめました。

僕のように、なかなか審査通過ができない方や、今後Google Adsenceの申し込みを実施される方のご参考になりましたら幸いです。

申請前のページ状態

f:id:rinne_grid2_1:20180508010234p:plain

1回目

記事に関する情報

記事数
550記事
各記事の文字数
50文字~1500文字の記事が混在

対応内容

  • はてなブログ無料会員から、有料会員(はてなブログPro)に変更(独自ドメインを設定するため)
  • 独自ドメインの設定
  • 問い合わせページ、プライバシーポリシーページの作成
  • ブログ紹介文の変更(対応後イメージ中の[1])
  • サイドメニューへの追加
    • プロフィールメニュー(対応後イメージ中の[2])
    • サイドメニューに問い合わせページとプライバシーポリシーページへのリンク(対応後イメージ中の[3])

対応後のイメージ

f:id:rinne_grid2_1:20180508010256p:plain

申請の時系列

  • 2018/03/27(火) YOUTUBEの審査基準(動画10,000再生)を満たしていないにもかかわらず、パートナープログラム経由でアドセンス申請し、広告設定。この時、いくつかの記事にアドセンスのコードを貼り付ける(そして翌日には貼付したこと自体を忘れる)
  • 2018/03/28(水) パートナープログラム経由でのアドセンスアカウントをキャンセルするため、キャンセルリクエスト送信
  • 2018/03/29(木) 改めて、通常の方法でアドセンス申請を実施
  • 2018/04/04(水) 結果連絡がなかなか来ないため、対応内容を振り返る。パートナープログラム経由で設定した広告のコードをいくつかの記事に貼付していたことに気づき、急ぎ削除。その直後、アドセンス不承認通知がメールで送られてくる

不承認メール内容(僕の場合は1回目~6回目まで全て同じ内容でした)

  • Googleからのメールを引用

Google のポリシーに準拠していないサイト: お客様のサイトは、Google AdSense のプログラム ポリシーに準拠していないか、ウェブマスター向けの品質に関するガイドラインに準拠していないため、現時点では AdSense のお申し込みを承認できませんのでご了承ください。Google の目標は、質が高く有用なコンテンツと価値あるユーザーに関連性の高い広告を配信できるサイトを広告主様に提供することです。お客様のサイトは、現在この基準を満たしていないと判断いたしました。

次のヒントを参考に、サイトでのユーザーの利便性を高め、AdSense の要件を満たすようにしてください。

• Google 広告を掲載するサイトでは、ユーザーに価値ある情報を提供することが重要です。サイトに掲載するコンテンツは、ユーザーが真っ先にアクセスしたくなるような、独自性と関連性の高いものにしてください。

• 自動生成されたページや独自のコンテンツがほとんどないページには、広告を掲載しないでください。

• サイトは操作しやすい構成にして、ユーザーの利便性を高めることが重要で、クリック操作でページを移動して、探している情報を簡単に見つけられるようにする必要があります。

2回目

不承認メール内容と、「Google Adsenceのプログラムポリシー」や「ウェブマスター向けの品質に関するガイドライン」を確認しつつ、メールにあった 「サイトは操作しやすい構成に」や「探している情報を簡単に見つけられるように」というヒントを元に、対応方針を決めました。

  • ナビゲーションメニューをPC版に追加する

記事に関する情報(1回目と同様)

記事数
550記事
各記事の文字数
50文字~1500文字の記事が混在

対応内容

  • ブログ記事のカテゴリの見直しを実施(カテゴリの付与されていない記事が多くあった)
  • 画面上部にPC用のナビゲーションメニューを追加し、ファーストビューでカテゴリごとの記事にアクセスできるようにした(対応後イメージ中の[1])

対応後のイメージ

f:id:rinne_grid2_1:20180508010314p:plain

申請の時系列

  • 2018/04/05(木) アドセンス再申請
  • 2018/04/06(金) アドセンス不承認

3回目

スマホ版については、記事の検索ボックスがページ上にあるため、ナビゲーションメニューは不要だろうと勝手に考えてしまっていたのですが・・・

ウェブマスター向けの品質に関するガイドライン」を改めて確認し

パソコン、タブレット、スマートフォンを含む、あらゆる種類やサイズの端末向けにサイトをデザインします。

という記載を読み・・・スマホ版のナビゲーションメニュー追加を決意しました。

記事に関する情報(1回目と同様)

記事数
550記事
各記事の文字数
50文字~1500文字の記事が混在

対応内容

  • Google Search Consoleとの連携を実施し、sitemap.xmlを登録
  • スマホ版のナビゲーションメニューを作成(対応イメージ中の[1])
    • Bootstrap(CSSフレームワーク)を利用(CDNからcss、jsを読み込み)
  • 記事の投稿
  • カテゴリ表示数の削減(1ページに100種類ものカテゴリを表示していた)

対応イメージ

  • スマホ版:対応前

f:id:rinne_grid2_1:20180508010521p:plain

  • スマホ版:対応後

f:id:rinne_grid2_1:20180508010530p:plain

申請の時系列

  • 2018/04/07(土)アドセンス再申請
  • 2018/04/11(水)アドセンス不承認

4回目

プライバシーポリシー等の必要ページを準備しつつ PC、スマホともにナビゲーションメニューの追加を行ったにも関わらず 不承認通知が来たため、ようやくここに来て記事内容の 見直しを行うことにしました。

記事に関する情報

記事数
300記事
各記事の文字数
大半が500文字以上

対応内容

  • 内容の薄い記事(500文字未満)や、プログラムソースが記事の大半を占めており、自分なりの解説やソースコメントを記載していない記事を「非公開」設定に変更

申請の時系列

  • 2018/04/12(木) アドセンス再申請
  • 2018/04/13(金) アドセンス不承認

5回目

さらに記事の見直しを行いました。

記事に関する情報

記事数
100記事
各記事の文字数
大半が600文字以上

対応内容

  • 内容の薄い記事(600文字未満)のものを「非公開」設定に変更
  • 過去記事(2011年頃)の記事で、体裁の整っていないものを「非公開」に設定(ex. 画像リンク切れ、レイアウト崩れ等)
  • 白背景+白文字等、クリック反転しないと視認できない文字の削除(自作ゲームの攻略記事に、行き詰まった人向けに、隠しテキストでクリア後のマップ用パスワードを記載していたのですが、念のため削除しました)

申請の時系列

  • 2018/04/14(土)アドセンス再申請
  • 2018/04/27(日)アドセンス不承認

6回目

ここまで来ると、わりと心が折れそうだったのですが・・・ 改善点を必死に探して申請しました

記事に関する情報

記事数
50記事
各記事の文字数
大半が800文字以上

対応内容

  • 読書メモ等の記事において、「引用」のみがメインとなっているものを「非公開」に設定
  • プロフィールアイコンを変更(RPGツクール用に作成されたドット絵を利用していたため。)

申請の時系列

  • 2018/04/28(月) アドセンス再申請
  • 2018/04/29(火) アドセンス不承認

7回目

記事に関する情報

記事数
25記事
各記事の文字数
800文字以上

対応内容

  • 記事のタイトルを改善([中級者向け]や、「便利」など)
  • クリックを誘導するかのように、誤解を与える文言が存在する記事があったため、削除(ex. 導入手順については下記のURLをクリックし・・・等)

申請の時系列

  • 2018/05/01(火) アドセンス再申請
  • 2018/05/06(日) アドセンス承認

まとめ

最終的に審査OKとなった理由は、断定はできませんが 下記のような対応が功を奏したのではないかと考えています。

  • プライバシー・ポリシーページ、問い合わせページの設置
  • PC、スマホへのナビゲーションメニューの追加
  • 記事タイトルの改善
  • URLクリックに関する文言の削除
  • 内容の薄い記事、極端に文字数の少ない記事の排除
  • レイアウト崩れ、画像リンク切れへの対応
  • カテゴリ表示数の削減

不承認続きで、心が折れかけましたが、諦めずに改善を続けて申請して本当に良かったと思います。

【intra-mart】横配置・縦配置ノードに展開されたノードIDから、展開元のノードIDを取得する方法

$
0
0

こんにちは。

今回は、intra-martに関する記事です。

縦配置・横配置ノード

横配置・縦配置ノードには、複数の処理対象者を設定することができます。 (横配置の場合は、必ず設定順に処理が流れ、縦配置の場合は設定した対象者が全て処理を完了した時点で次の処理に流れるということが実現できます。)

つまり、稟議書のワークフロー等、複数の承認者が必要な場合には必須となってくるノードかと思います。

ノードとステータスの紐づけの要件があったら

例えば、業務ワークフローにおいて、各ノードが終了する度に、ステータスを更新するような要件が発生したとします。

  • 0: 未申請(申請前)
  • 1: 申請OK(申請終了)
  • 2: 複数承認OK(横配置終了)
  • 3: 同期承認OK(縦配置終了)
  • 4: 申請業務終了(案件終了)

その場合、ルート定義で設定したノードIDを元に、LogicDesigner内で分岐して、 対象のノードに到達した時点でDBにステータス更新を行えば良い・・・という感じに思ってしまうのですが・・・

横配置・縦配置それぞれが実際にワークフローとして動作する際には、 ルート定義で設定したノードIDではなく、動的に割り当てられたIDがセットされる仕様になっています。

そこで今回は、下記の機能を利用し、動的に割り当てられたノードIDから、ルート定義で設定したノードIDを取得する方法についてまとめます。

利用する機能

  • IM-BIS
  • IM-FormaDesigner
  • IM-LogicDesigner
    • ユーザ定義(スクリプト)
  • IM-Workflow
    • 到達処理
  • データソース定義

IM-BIS定義を作成し、ルート定義の設定とフォーム設定を行う

ルート定義

横配置、縦配置の順にノードを配置し、それぞれのノードIDは「yoko_node」「tate_node」に設定しました。

f:id:rinne_grid2_1:20180520084741p:plain

f:id:rinne_grid2_1:20180520084800p:plain

フォーム設定

IM-BISからIM-FormaDesignerを開き、簡易的なフォームを配置します。

f:id:rinne_grid2_1:20180520085154p:plain

フォーム共有で横配置・縦配置それぞれに共有し、「定義の反映」実行

共有

  • フォームを共有

f:id:rinne_grid2_1:20180520085450p:plain

貼り付け

  • 横配置・縦配置にそれぞれフォームを貼付

f:id:rinne_grid2_1:20180520085638p:plain

定義の反映

  • 忘れてはいけない、定義の反映を実行

f:id:rinne_grid2_1:20180520085834p:plain

LogicDesignerのフロー定義を作成

入出力設定

  • 到達処理で、動的に割り当てられたノードIDを取得するため、暗黙的なパラメータをセットします
    • 入力:システム案件IDとノードIDを設定
    • 出力:メール送信可否フラグを設定(mailSendFlagはboolean)

f:id:rinne_grid2_1:20180520094556p:plain

到達処理で割当可能なパラメータについては、下記のintra-martのリンクをご参照ください。

5.4. 到達処理 — IM-Workflow プログラミングガイド   第16版 2016-08-01   intra-mart Accel Platform

定数設定

  • 到達処理では、必ずメール送信可否フラグを返却する必要があるため、今回は定数でtrueを設定しておきます

f:id:rinne_grid2_1:20180520090943p:plain

ユーザ定義(JavaScript定義)を作成します

f:id:rinne_grid2_1:20180520091156p:plain

パラメータの設定

  • 到達処理で受け取ったパラメータをJavaScript定義に渡すため、入力パラメータにシステム案件IDとノードIDを設定します

f:id:rinne_grid2_1:20180520092551p:plain

スクリプトの作成

  • JavaScriptを記述します
    • ロケールコードとシステム案件IDを利用し、未完了案件ノードを取得
    • 未完了案件ノードから、ノード設定情報を取得
    • 取得できた場合、ノード設定情報から、展開元のノードIDを取得
function run(input) {var systemMatterId = input.systemMatterId;
    var nodeId = input.nodeId;
    
    var actvMatterNode = new ActvMatterNode("ja", systemMatterId);
    var config = actvMatterNode.getExecNodeConfig(nodeId);
    if(config.resultFlag && config.data !== undefined) {var originalNodeId = config.data.expansionOriginalNodeId[0];
      Debug.console("展開ノードID:" + nodeId + " 展開元ノードID:" +originalNodeId);
    }return{};
}

上記LogicDesigner定義でデータソース定義を作成

f:id:rinne_grid2_1:20180520093139p:plain

f:id:rinne_grid2_1:20180520093251p:plain

f:id:rinne_grid2_1:20180520093347p:plain

コンテンツ定義で、到達処理を設定

f:id:rinne_grid2_1:20180520093522p:plain

f:id:rinne_grid2_1:20180520093727p:plain

サンプルデータの青柳さんでログインして、申請を実施

(ロール:BIS担当者を付与していないと、申請時に権限エラーで落ちるので、注意しましょう)

f:id:rinne_grid2_1:20180520100757p:plain

f:id:rinne_grid2_1:20180520100909p:plain

横配置ノードに到達し、到達処理が動く

  • ノードIDが取得できたことが確認 (e Builderのコンソールより)

f:id:rinne_grid2_1:20180520101200p:plain

これで、現在ノードIDの展開元が何なのかを知ることができました。

ただし、「動的承認ノード」でも到達処理が動いてしまうため、動的承認ノードが存在する場合は、 ルート定義のIDを用いて、別途分岐を行う必要があります。 (動的承認ノードの場合は、ルート定義のノードIDがそのまま渡されるため、分岐に利用できます)

function run(input) {var systemMatterId = input.systemMatterId;
    var nodeId = input.nodeId;
        var dynamicNodeList = ["douteki_node1", "douteki_node2"];

        // 到達処理で渡されたノードIDが動的承認ノードのものではない場合、横配置・縦配置として展開元IDを取得if(dynamicNodeList.indexOf(nodeId) < 0) {var actvMatterNode = new ActvMatterNode("ja", systemMatterId);
        var config = actvMatterNode.getExecNodeConfig(nodeId);
        if(config.resultFlag && config.data !== undefined) {var originalNodeId = config.data.expansionOriginalNodeId[0];
          Debug.console("展開ノードID:" + nodeId + " 展開元ノードID:" +originalNodeId);
        }}else{// 動的承認ノードの処理}return{};
}

以上で終わります。 皆様も素敵なintra-martライフを。

【.htaccess】URLにある文字を含む場合、そのままのパラメーターでリダイレクトする方法

$
0
0

こんにちは。今回はApache HTTP Serverと.htaccessに関する記事です。

基本的にはWebフレームワークのURLに飛ばして問題ないけど、 あるURLの場合には静的なページに遷移させたいことってありますよね。

  • こんな感じ
    • http://hoge.co.jpで、CakePHPアプリケーションにアクセス
    • http://hoge.co.jp/any_str/hogera.htmlには、別の静的ファイルを配置してアクセスさせたい
    • (上記例ではコロンを全角にしています)

その場合、.htaccesに下記の記述を行うことで実現可能です。

  • .htaccessに以下を指定する
RewriteCond %{REQUEST_URI} /any_str/
RewriteRule ^(.*)$ $1 [L]

こうすることで、リクエストURIにany_strを含む場合、それ以降のRewriteRuleの評価をせず、リクエストされたURIに転送されるようになります。

具体的な利用例

CakePHPにおいて、ある文字を含む時はCakePHPのアプリケーションに転送させたくない場合

# CAKEPHPDIR/.htaccess
<IfModule mod_rewrite.c>
        RewriteEngine on
        RewriteBase /
        RewriteCond %{REQUEST_URI} /any_str/
        RewriteRule ^(.*)$ $1 [L]

        RewriteRule ^$ app/webroot/ [L]
        RewriteRule (.*) app/webroot/$1 [L]
</IfModule>

CakePHP等のフレームワークとは別に、静的サイトやCGIアプリ等を配置している場合などに使えそうですね。


【RPGツクールMV】[ビギナー向け]Sprite(Sprite_Base)にアニメーションを表示する方法

$
0
0

こんにちは。

タイトルの件ですが、RPGツクールMVのスクリプトで作成した「Sprite_Base」に アニメーションを表示したいときってありますよね。

通常はマップ上に表示されたキャラクターに対して、イベントコマンド(アニメーションの表示)で アニメーションを制御するかと思います。

自作シーンの処理でアニメーションを使いたいけど・・・

でも、自作のシーンとメニューを作成し、その処理の中で画像にアニメーションを適用するとなると、 どうやったらいいのか、悩ましいですよね。(僕は結構悩んでしまいました)

そこで、この記事ではSpriteとして読み込んだ画像に、アニメーションを表示する方法を ソースコードを記載いたします。

前提

  • Sceneの仕組みがなんとなくわかっている
  • Sprite_Baseに画像を読み込んで操作したことがある

アニメーションを表示するためのソースコード

  • Sprite_Baseのメソッド:startAnimationを利用します
    • Spriteに対して、アニメーションを表示
      • startAnimation(animation, mirror, delay)
      • animation: $dataAnimations[n]のアニメーションオブジェクトを指定する(nにはデータベースのアニメーション番号を指定します)
      • mirror: アニメーションを判定して表示するかどうかをtrue, falseで指定します
      • delay: フレーム数を指定し、その分だけアニメーションの開始を遅延させます

アニメーションを表示するシーンクラス

// my_scene.jsfunction Scene_MyMenu() {}
Scene_MyMenu.prototype = Object.create(Scene_Base.prototype);
Scene_MyMenu.prototype.constructor = Scene_MyMenu;
Scene_MyMenu.prototype.initialize = function() {
  Scene_Base.prototype.initialize.call(this);
}
Scene_MyMenu.prototype.create = function() {
  Scene_Base.prototype.create.call(this);
  this.createWindowLayer();
  // ここから開始var sprite = new Sprite_Base();
  sprite.bitmap = ImageManager.loadPicture("画像ファイル名");
  var animation = $dataAnimations[1]; // 表示したいアニメーションのデータベース番号
  sprite.startAnimation(animation, false, 0);
  // ここで終了this.addChild(sprite);
}

上記シーンクラスを呼び出すためのコード

マップにキャラクターを配置した上で イベントコマンド「スクリプト」で下記のソースコードを記載しましょう。 対象のキャラクターのイベントが開始すると、シーンが切り替わり指定した画像が表示され、 同時にアニメーションが表示されます。

SceneManager.push(Scene_MyMenu);

表示方法については、参考にできるソースが 「rpg_objects.js」や「rpg_sprites.js」にあります。

上記Spriteを表示するコマンドをメソッド化しておき、 コマンドを選択した時やウィンドウを閉じる時など、 各タイミングで呼び出す等、色々な場面で利用できそうですね。

【読書メモ】あなたの話はなぜ「通じない」のか-part1

$
0
0

こんにちは。今回は山田ズーニーさんの本「あなたの話はなぜ「通じない」のか」について 読書メモと感想を記述します。

あなたの話はなぜ「通じない」のか (ちくま文庫)

あなたの話はなぜ「通じない」のか (ちくま文庫)

本記事の読書メモの定義

  • まず以下の基準で書籍内容を引用させていただきます。
    • 僕自身が読書していて、心に残った言葉
    • 書籍に線を引いた文章(電子書籍の場合マーカー引いた文章)
  • 記事の最後に個人的な感想を述べます(状況によっては、心に残った言葉の直後に感想が入ることもあります)

表記方法

  • 引用について

このようなボックスの中に記載されている文章は 引用を表しています

それでは早速、読書メモを記述いたします。

目次

読書メモ

発信者によって印象が変わってしまう

同じことでも、あなたが言うのと別の人が言うのでは、 与える印象がまるで違う。人間もメッセージを伝えるメディア(=媒介)だとすれば あなたは相手からどんな風に見られているだろうか

話が通じるために実行すべきこと

日ごろから人との関わり合いの中で、 自分というメディアの信頼性を高めていく必要がある。

話が通じるためにおさえておきたい基礎

  • 自分のメディア力を上げる

あなたという人間への信頼、共感を高めながら相手に言いたいことを伝えるか、その技術をつかむ。 話が通じるとは"勝ち負けでなく、あなたと相手の間に橋を架けるようなものだ"

  • 相手にとっての意味を考える

相手とはどんな人か?
例えばあなたが「恋人と映画に行った」ことを話すとする。
もし相手が映画好きで感性が合う人なら、映画の情報として「役立つ知らせ」という意味がでてくる。
また相手が沈んでいて、あなたの話が笑えるものだったら「気が晴れる楽しい話」という意味がでてくる。
相手へのメリットが何もなかったとしたら、あなたの話は「単なる自慢」とうつるかもしれない。

人は自分に関係ない、意味のない話はなかなか聞こうとしないものだ。
まずこの厳しい現実をしっかり押さえよう

  • 自分が一番言いたいことをはっきりさせる

自分が一番言いたいことを、極力はっきりさせておき、 それを頭か結論にもってくるだけで、ずいぶん話は通じやすくなる。 「言いたいことはあるんだけど、自分でもはっきりしない」という状態で切り出すと 相手までモヤモヤさせてしまう

  • 意見の理由を説明する

自分に都合のいい理由ばかりでたたみかけてもだめだ。 あなたと、相手の真ん中に「理由」がある。理由を、自分側、相手側、あるいはもっと普遍的な 角度からと、多角的に引いてくる方法や、論理的な説得の筋道を組み立てていく技術もつかもう

  • 自分の根っこの想いにうそをつかない

その人の根っこにある想い・発言の動機、これを「根本思想」という。 「言葉はちょうど氷山の見えるところのようなもので、水面下には、その何倍もの大きな その人の生き方や価値観が横たわっている」
本書は「自分に想いにうそをつかない」ということを一歩も譲らず考えた。

感想

書籍で述べられていることに関して、読んだ直後は「当たり前じゃん」となっているものの、 実際の会話では意識できていないということが多いのではないでしょうか。

僕自身は、当然実践できていませんし、実践する前の段階で止まってしまうように思います。

「仕事での報告」に限り、事実を手短に話すことを意識してはいるものの、 自分が一番言いたいこと(キーセンテンスやキラーメッセージ)を常に意識できておらず、なんとなく話してしまうこともあります。

さらに一番やってはいけないこと・・・話をする際に、「前提は知っているだろう」ということで、 前提事項や理由を省くこともあります。

これらの基礎を元に、今まで「話が通じなかった」と感じた時の経験を思い出し 「自分はダメだ」で終わるのではなく、原因と対処方法を考えるべきだと強く感じました。

結局のところ、「通じない」痛みによって当時の「思い出」がマイナスイメージとなっていることから、 傷つくのが嫌で、どうにも重い腰が動かせないのではないのかなと思います。

どこかの本で読んだか忘れてしまったのですが、 "自分が伝える"ことが大事なのではなく、"相手に伝わる"ことが大事なんですよね。

part2はこちら

www.rinsymbol.net

【読書メモ】あなたの話はなぜ「通じない」のか-part2

$
0
0

こんにちは。前回に続いて、山田ズーニーさんの本「あなたの話はなぜ「通じない」のか」について 読書メモと感想を記述します。

あなたの話はなぜ「通じない」のか (ちくま文庫)

あなたの話はなぜ「通じない」のか (ちくま文庫)

前回の記事

www.rinsymbol.net

本記事の読書メモの定義

  • まず以下の基準で書籍内容を引用させていただきます。
    • 僕自身が読書していて、心に残った言葉
    • 書籍に線を引いた文章(電子書籍の場合マーカー引いた文章)
  • 記事の最後に個人的な感想を述べます(状況によっては、心に残った言葉の直後に感想が入ることもあります)

表記方法

  • 引用について

このようなボックスの中に記載されている文章は 引用を表しています

それでは早速、読書メモを記述いたします。

目次

読書メモ

コミュニケーション技術を磨いてどうなりたいのか

ある人は「交渉で常に自分が勝てるようになりたい」と言うかもしれない。
またある人は「人間関係で傷つかないようになりたい」と言うかもしれない。
あなたの目指すコミュニケーションのゴールは何だろう?

【Memo】
じぶんのゴール

交渉やお客様との打ち合わせで、
相手の話に真剣に耳を傾け、相手の抱える問題を解決して、信頼し合う関係を作りたい

著者(山田ズーニーさん)のコミュニケーションゴール

自分の想いで人と通じ合うこと

伝えなければ、伝わらない

わかってもらえないと思うとき、落ち込んだり、相手をうらんだりする前に
二つ自問してみよう。

  • わかってもらうために自分はどんなことをしてきたか?
  • 自分は人のことをわかろうとしているのか?
【Memo】
言わなきゃ、結局何もわからない。
自分の言葉で、伝えるように心がけている。

「黙っていい仕事をしていればわかってもらえる」と私は思っていた。
でも多くの人が忙しく、さまざまな仕事をしている会社では、
人の仕事をいちいち見てはいられない。
仕事の成果を見ても、どうやってつくったかプロセスまでわからない。
ましてや隣りの席に居る一の頭の中の、まだ形にしていないものを、
一体どうやってわかれというのだろう

感想

わかってもらうために自分はどんなことをしてきたか?

自分は人のことをわかろうとしているのか?

この問いは、僕にとって非常に厳しいものでした。 二つの問いを突き付けられ、何か、心に刺さるような感覚があったのです。

わかってもらうために、何をしてきたか?

確かに何冊か、コミュニケーションに関する本を読みました。 しかし、実際に人とのコミュニケーションで、実践していません。

人のことをわかろうとしているのか?

「興味がない話は理解できない」とそもそも聴く気を失っていることがあるように思います。

読書を進めて見えてきた願望

お客さんに「もう一度おっしゃっていただけませんか」と言えるようになりたいと考えました。 基本ですが、お客さんの分野の知識を理解していない状態で話を聞くと、わからないことだらけになってしまいます。 そして、説明のお願いを躊躇してしまうのです。

今後について

ポイントを絞って話をすること、相手の話を要約すること、 相手の言ったことを理解すること、自分の伝えたいことを明確にしておくこと

これらがうまくできず、怖くて逃げている節があるように思いました。

もっと相手を決めつけず、相手のことを考えることができれば、生きやすくなるのではないかと感じました。

【Angular】JSONPの取得方法がわかったのでサンプルを作った話(HttpClient jsonp)

$
0
0

こんばんは。今回はAngularの話です。

今日は休日ということもあり、以前購入した「Angularアプリケーションプログラミング」の本を読みすすめていました。

Angularアプリケーションプログラミング

Angularアプリケーションプログラミング

その中で、はてなブックマークのAPIを使って、 ブックマークエントリー情報をJSONP形式で取得するという内容があったのですが・・・書籍の発売当時のAngularバージョンを元にして書かれているため、 うまく動きません。

そこで、JSONP形式による通信方法について調査し、現行バージョンでの取得サンプルを作成しました。

目次

サンプルプログラムのページ

はてなブックマークエントリ情報APIで、指定したURLのブックマーク情報を取得します。 初期URLは僕が作成した、ツクールMVのプラグインURLが指定されています。

https://rinne-grid.github.io/index.html

(スクリーンショット)

  • 初期画面 f:id:rinne_grid2_1:20180527212431p:plain

  • ブックマークエントリー取得 f:id:rinne_grid2_1:20180527212442p:plain

サンプルプログラムの置き場所

Githubに配置しました。

github.com

  • 今回、@angular/materialを使って画面を作成していますが、この記事の本筋ではないので、コードの記載は省略します。
  • 全ソースが下記にありますので、git cloneして動作確認等にご利用ください。

Angularアプリの作成

Angularアプリケーションの作成については、公式ドキュメントをご参照ください。

ざっくりと下記のような感じです

  • npmの場合
$ npm i -g @angular/cli
$ ng new appname
  • yarnの場合
$ yarn global add @angular/cli
$ ng new appname

app.modules.tsの記述

  • ポイント
    • @angular/common/HttpClientを利用するため、HttpClientModuleをインポートする
    • HttpClientのjsonpを利用するため、HttpClientJsonpModuleをインポートする
import{ BrowserModule }from'@angular/platform-browser';import{ BrowserAnimationsModule }from'@angular/platform-browser/animations';import{ NgModule }from'@angular/core';import{ HttpClientModule, HttpClientJsonpModule }from'@angular/common/http';import{ FormsModule }from'@angular/forms';import{ AppComponent }from'./app.component';@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [// @angular/core
    BrowserModule,
    HttpClientModule,
    HttpClientJsonpModule,
    BrowserAnimationsModule,
    FormsModule,],
  providers: [],
  bootstrap: [AppComponent]})exportclass AppModule {}

app.component.htmlの記述

  • ポイント
    • はてなブックマークエントリーAPIを呼び出す契機となるボタンを配置する
    • 取得するブックマークエントリの対象となるURLを入力するテキストボックスを配置する(コンポーネントのtargetUrlと双方向バインド)
<inputname="targetUrl" [(ngModel)]="targetUrl"><buttonname="getBookmarks"  (click)="getBookmark()">取得</button>

app.component.tsの記述

import{ Component }from'@angular/core';@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']})exportclass AppComponent {

  targetUrl ='https://github.com/rinne-grid/tkoolmv_plugin_RecollectionMode'constructor(){}
  getBookmark(){
    console.log(this.targetUrl);}}

サービスクラス(hatena-bookmark.service.ts)の作成

ng generateの利用

  • コンソール、ターミナルでcliを実行し、サービスクラスのテンプレートを作成
$ ng generate service hatena-bookmark

サービスクラスの記述

import{ Injectable }from'@angular/core';import{ HttpClient, HttpParams }from'@angular/common/http';import{ Observable }from'rxjs';@Injectable()exportclass HatenaBookmarkService {constructor(private http: HttpClient){}// はてなブックマークエントリ情報取得用のAPIエンドポイントprivate apiEndPoint: string="https://b.hatena.ne.jp/entry/json/";

    getBookmarks(targetUrl:string): Observable<any>{
        console.log(targetUrl);// API仕様によると、urlとcallbackを指定するとjsonpを戻すとのこと// callbackはjsonpの第2引数で指定するlet httpParams =new HttpParams()
            .set("url", targetUrl);// httpParamsにパラメータを設定した状態でtoStringを実行すると、key=value形式で返してくれる// https://angular.jp/api/common/http/HttpParams#toString
        console.log(`${this.apiEndPoint}?${httpParams.toString()}`)returnthis.http.jsonp<any>(`${this.apiEndPoint}?${httpParams.toString()}`,"callback");}}

サービスクラスをapp.module.tsのprovidersに追加

import{ BrowserModule }from'@angular/platform-browser';import{ BrowserAnimationsModule }from'@angular/platform-browser/animations';import{ NgModule }from'@angular/core';import{ HttpClientModule, HttpClientJsonpModule }from'@angular/common/http';import{ FormsModule }from'@angular/forms';import{ AppComponent }from'./app.component';//-------------------------------------------------------// 追加//-------------------------------------------------------import{ HatenaBookmarkService }from'./hatena-bookmark.service';@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [// @angular/core
    BrowserModule,
    HttpClientModule,
    HttpClientJsonpModule,
    BrowserAnimationsModule,
    FormsModule,],
  providers: [//-------------------------------------------------------// 追加//-------------------------------------------------------
    HatenaBookmarkService
  ],
  bootstrap: [AppComponent]})exportclass AppModule {}

app.component.tsの修正

  • ポイント
    • HatenaBookmarkServiceをDIする
    • getBookmarkメソッドから、HatenaBookmarkServiceのgetBookmarksメソッドを呼び出す
    • サービスクラスのgetBookmarksの戻り値(Observable)をsubscribeし内容を取得する
import{ Component }from'@angular/core';import{ HatenaBookmarkService }from'./hatena-bookmark.service';@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']})exportclass AppComponent {
  targetEntry: any=null;
  targetUrl ='https://github.com/rinne-grid/tkoolmv_plugin_RecollectionMode'constructor(private hatenaService: HatenaBookmarkService){}
  getBookmark(){
    console.log(this.targetUrl);this.hatenaService.getBookmarks(this.targetUrl).subscribe((entries)=>{this.targetEntry = entries;
        console.log(entries);});}}

アプリを実行

$ ng serve

http://localhost:4200に接続する

この記事内のソースコードは@angular/materialに関する記述を除いているため、 リンク先のようなサンプルにはならないのですが、HttpClient.jsonpを利用して、 JSONP形式のデータを取得できたことがわかるかと思います。

StackBlitzに本記事のソースコードのみで作ったサンプルを配置しましたので、こちらもご利用ください

(StackBlitz)本記事内のソースコードのみのバージョン

stackblitz.com

ハマったところ(あまりJSONP関係ない)

  • Github Pagesにサンプルを上げたとき、はてなブックマークAPIのURLがhttpで、GithubPagesがhttpsだったのでMixedコンテンツになってしまってうまく動かなかった
    • APIがhttpsも対応していたため、httpsに変更
    • 同様に、StackBlitzからのリクエストもhttpsに変更したら動いた
  • HttpParamsにパラメータをセットする際にインスタンスを作成した後にappendやsetを実行してもうまくセットされなかった
// ダメだったlet httpParams =new HttpParams(); 
httpParams.set("hoge","foo");
// うまくいったlet httpParams =new HttpParams().set("hoge","foo");

AngularによるJSONP取得ですが、だいぶ簡単に書くことができて、非常に便利だと思いました。 HttpClient.get, post, put等だとCORS対策等でハマりがちなのですが、 JSONPの場合は(開発の時点では)あまりハマることなくすんなりいけたように思います。

それでは、皆さんも楽しいAngularプログラミングを。

【雑記】Vue.js学習時の思考パターンに関するメモ

$
0
0

最近はKindleの「速習Vue.js 速習シリーズ」書籍を利用して、Vue.jsの学習を行っていました。

速習Vue.js 速習シリーズ

速習Vue.js 速習シリーズ

ふと、自分自身が学習時にどんなことを考えているのか(思考パターン)が気になってしまい、 その時点の思考についてまとめてみることにしました。

「テンプレート側でデータオブジェクトにアクセスするには、{{ ... }}(Mustache)構文を利用します」という文章を読んだ時の思考

  • 確かマスタッシュって名前の式構文だったなー。口ひげみたいな形だから、そう呼ばれているんだったか。
    • Angularでも同じ書き方するよな。
    • Django Templateもコンテキスト変数を表示するときにマスタッシュ記号使うし、Vue.jsやAngularをDjangoTemplateと一緒に利用しようとする競合するね
  • あれ、Django Template以外にもJinja2ってあったような気がする。それもマスタッシュ構文使うんだっけ?
  • 調査後、同じ記号利用することが判明
  • Angularでいうところのパイプとか、Djangoでいうフィルターみたいに、テンプレートの変数に対して加工を行うこともできそうな気がする。
    • 調べたら出てくるだろうけど、本で取り上げてない内容を先に確認するのはなんか気が引ける→とりあえず保留で。

{{ ... }}(Mustache)式の中でJavaScript組み込みオブジェクトが利用できるという文章を読んだときの思考

  • ということは、自作オブジェクトも登録するって方法もあるんだろうか?
    • なんなくあまりやらない方が良さそうな気がするけど方法は気になる。
  • きちんとした方法が後のページど出てくるかもしれないから、その時に振り返ればいいか
    • Vue.jsで定義されている何らかのグローバル変数に対して、追加したいオブジェクトを指定してあげる感じなんだろうか
  • それにしても、JavaScript組み込みオブジェクトといえば・・・サイ本購入したのは良いけど、最後まで読めてない気がする。何度も読み直すくらいしなきゃいけないな

JavaScript 第6版

JavaScript 第6版

コンポーネントに関する文章を読んだ時の思考

Vue.component('my-hello', {
  template: '<div>こんにちは、コンポーネント</div>'
});
  • Vue.component関数にコンポーネント名とオブジェクトを指定するのか
    • templateプロパティで、HTMLタグを指定しているなー
    • ちょっと前に書いたJSXを思い出す気がする
    • Angularの@Componentデコレータの中身に雰囲気が似ている気がする
    • こんな感じのやつ
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']})
  • コンポーネントの利用方法は、指定したIDをHTMLのコードの中に記載すれば良いとのこと
    • コンポーネントの利用方法については、AngularやReactと同じような感じに見える
    • ということは、コンポーネントに親子関係も発生しそうだし、なんとなくReactのpropsやstateみたいな、値の定義とその値を親子間で受け渡す方法がほぼほぼ提供されてそうな気がする

vue-cliに関する説明文章を読んだときの思考

  • Vueにもcliがあるのか
  • Angularだと、ng newで雛形が作成されていたけど、vueの場合はどんな感じなんだろう
    • vueの場合も同じように雛形が作成されるけど、最初に色々と設定を聞かれるのね
    • .vueファイルってのができてるけど、これは・・・?
      • テンプレートとスクリプト、スタイルをまとめて記載したものらしい
      • style scopedって感じで、styleタグにscopedが追加されているけど、これはいったい何を示しているのか?
        • スコープって名前から推測すると、まずAngularのコンポーネントにおけるスタイルの適用範囲を思い出す
        • Angularの場合、そのコンポーネントで指定したスタイルはそのコンポーネント(と配下も?)のみに適用されたような気がする
        • ということは、Vue.jsも同じような感じで、適用範囲が現在のコンポーネントに絞られるのかもしれない

まとめ

今回学習しているVue.jsは、今までに書いたコードがある既存のフレームワークと似通っている部分が多かったため、 「比較」と「推測」が主な思考パターンになっているようでした。

基本的には、今までに体験・経験したことがあるかどうかを考えているようです。 該当するものがなければ、"そういうものだ"と、一時的に認識しておき、あとで整理するような感じになってそうです。

今まで見たことも経験したこともないものを学習する場合、 いったいどんな思考パターンになるのか、今後機会があれば試してみようと思います。

【intra-mart】intra-martの検証環境をDockerを使って爆速で構築する(docker-for-intra-martで環境構築)

$
0
0

こんにちは。

intra-martの環境構築ってすごく面倒くさいですよね。

TL;DR

resin-proとimart.warさえ準備すれば、1時間かかってた環境構築作業が15分で終わって、 いい感じにDockerコンテナ上でintra-martが動くので、検証作業が楽になって幸せ。

目次

intra-mart(イントラマート)とは

「そもそもintra-martって何?」という感じかもしれません。

intra-martはNTTデータイントラマート社が提供する、企業向けのシステム共通基盤です。

個別最適化された企業内のIT環境を一つに集約し、業務アプリケーションの全社横断的な運用を可能にするシステム共通基盤(開発・運用フレームワーク)です

「システム共通基盤/PaaS | NTTデータイントラマート」ページ説明より引用

稟議や手続きなどの承認フローの作成機能や、Webブラウザ上でのドラッグ&ドロップによる画面開発機能、 その他グループウェア、コラボレーション等の機能が提供されています。

勤め先でこの基盤が導入されている方であればご存知かもしれません。

企業競争力を強化する製品・サービス群 | システム共通基盤の株式会社NTTデータイントラマート

環境構築時の課題

僕は、現在勤めている会社でintra-martを利用して、アプリ開発・保守や技術検証等を実施しています。

つまり新機能の検証なども実施するため・・・ 例えばアップデートによって追加された新機能をちょっと試したいけど、既存の環境は汚したくないという場合には セットアップ直後のまっさらの状態の環境が必要になります。

しかしintra-martは環境構築作業だけでかなりの時間がとられてしまうのです。

環境構築作業一覧

下記に、環境構築時に実施する必要のある作業を一覧化してみましたが・・・多すぎる

  • JDKのインストール
  • resin-proのセットアップ
    • intra-martのダウンロードライブラリから、resin-pro-x.x.x.tar.gzをダウンロードし展開
    • プロパティの設定
    • Redhat EnterpriseやCentOSのインストール
    • resinのmakeに必要なパッケージ・ライブラリのインストール
    • make実行
  • Jugglingプロジェクトの作成、warファイル出力
  • データベースのセットアップ
    • インストーラのダウンロード
    • ミドルウェアのインストール
    • データベース作成
    • スキーマ、ユーザ作成
    • 権限付与
  • warファイルのデプロイ
  • intra-martのテナント環境セットアップ

慣れていれば1時間もかからず作業を終えてしまえるのですが、そうでない場合は3時間程度はかかってしまうこともあります。 そしてとにかく、ダウンロードやインストール、セットアップ等々、手動で何度も繰り返し実施するのは非常に面倒です。

改善方法の検討→環境構築を部分的に自動化

とにかく、手動作業が多すぎて嫌になりました。 そこで今回は最近学習を始めたDockerを利用して、自動かつ爆速(誇張表現?)でintra-mart環境を構築できるようにしてみました。

Dockerプロジェクトの配置先(Githubリポジトリ)

Githubにアップしました。

github.com

Dockerでやること

  • JDKのインストール
  • resin-proのセットアップ
    • CentOSのインストール
    • resinのmakeに必要なパッケージ・ライブラリのインストール
    • make実行
  • データベースのセットアップ
    • インストーラのダウンロード
    • ミドルウェアのインストール
    • データベース作成
    • スキーマ、ユーザ作成
    • 権限付与

手動でやること

  • resin-proのセットアップ
    • intra-martのダウンロードライブラリから、resin-pro-x.x.x.tar.gzをダウンロードし展開
    • プロパティの設定
  • Jugglingプロジェクトの作成、warファイル出力
  • warファイルのデプロイ
  • intra-martのテナント環境セットアップ

Dockerで作成するintra-martのシステム構成

Docker上の環境

ホスト名コンテナイメージ目的
apCentOS 7.5.1804(※1)(library/centos)resin-proの実行及びwarデプロイ
dbPostgreSQL 10(library/postgres)intra-martに関するデータの保存
adminerAdminer(library/adminer)dbのデータ参照用のアプリ

(※1 厳密には、NTTデータイントラマート社はintra-martが動作するLinux環境として、Red Hat Enterprise Linux 6.x、7.xのみを動作保証しているため、 本Docker関連ファイルを利用する場合は、あくまでも動作検証用の環境に留めておくことをおすすめします。本記事の内容によって発生した障害等について、一切責任を負いません)

必要なPC環境

  • 下記のいずれか
OSバージョンDocker
Windows7以降64bitDocker Toolbox
Windows1064bitDocker for Windows
macOS-Docker for Mac

なお、試してはいませんがVT-xを有効にする等の対応によって 32bit環境でもDockerが利用できるようなので、32bit環境でも実行できるかもしれませんね。 (僕は32bitのPCは持っていないので、試せていません)

利用手順

ここからは、本Dockerプロジェクトを利用して、intra-martの環境を構築する手順を記述いたします。 (この手順は、Windows10 + Docker Toolboxを前提にしています。必要に応じて適宜読み替えをお願いします。)

[1] Docker Toolboxのインストール

  • 下記のURLより、Docker Toolboxをダウンロードし、インストールします docs.docker.com

[2] Dockerのメモリを増やす

  • Dockerで利用するメモリを確保するため、VirtualBoxから、メモリを増やしておきます。
    • 僕のPCはとりあえず5GBを設定しています
  • 下記のURLの記事にメモリを増やす方法が詳しく記載されています qiita.com

  • Docker for Windowsの場合は、下記のURLが参考になります qiita.comタスクトレイのDockerアイコン右クリック->Settingsを開く

  • Docker for Macの場合の場合、下記のURLが参考になります qiita.com Preferences → Advanced にあるMemoryで使用量を調節

[2] Gitのインストール

  • 下記のURLより、Git for Windowsをダウンロードし、インストールします gitforwindows.org

[3] Dockerプロジェクトのダウンロードと初期設定

  • 任意のフォルダで、以下のコマンドを実行し、dockerプロジェクトをダウンロードします
> git clone https://github.com/rinne-grid/docker-for-intra-mart im
>cd im
  • warファイルの配置用フォルダを作成します
>mkdir .\ap\war

[4] Jugglingでwarファイルを作成

プロジェクト名をimartにして、必要なモジュールを選択し、設定を行います。 今回のDocker環境をそのまま利用するためには、下記ファイルの設定を変更する必要があります

  • storage-config.xmlを設定する
  • resin-web.xmlを設定する
  • 出力するwarファイル名をimart.warとする

(Dockerプロジェクトのap/.envファイルを変更することで、別の値を指定することも可能です)

storage-config.xmlの設定

imart/config/storage-config.xml の 19行目付近を以下のとおりに変更します

<root-path-name>/im-data/storage</root-path-name>
resin-web.xmlの設定

imart/resin-web.xml 内容を下記のとおりにします

<web-app xmlns="http://caucho.com/ns/resin"xmlns:resin="urn:java:com.caucho.resin"><character-encoding>UTF-8</character-encoding><log-handler name=""class="jp.co.intra_mart.common.platform.log.handler.JDKLoggingOverIntramartLoggerHandler"/><logger name="debug.com.sun.portal"level="warning" /><!-- im_service(im_asynchronous) --><resource jndi-name="jca/work"type="jp.co.intra_mart.system.asynchronous.impl.executor.work.resin.ResinResourceAdapter" /><jsp><recycle-tags>false</recycle-tags></jsp><database jndi-name="jdbc/default"><driver><type>org.postgresql.Driver</type><url>jdbc:postgresql://db:5432/imart</url><user>imart</user><password>imart</password><init-param><param-name>preparedStatementCacheQueries</param-name><param-value>0</param-value></init-param></driver><max-connections>20</max-connections><prepared-statement-cache-size>8</prepared-statement-cache-size></database><session-config><reuse-session-id>false</reuse-session-id><session-timeout>30</session-timeout></session-config><mime-mapping extension=".json"mime-type="application/json"/></web-app>
warファイルの出力

imart.warという名称でwarファイルを出力したら、 プロジェクトのim/ap/warフォルダの中に、warファイルをコピーします

[5] intra-martのサイトからLinuxのresin-proをダウンロード

intr-martのサイトにアクセスし、プロダクトファイルダウンロードボタンを押下します。

www.intra-mart.jp

ライセンスキーを入力すると、ダウンロード可能なファイル一覧が表示されます。

なお、intra-martサイトにも書いているとおり、.tar.gzがLinux用のresin-proになります。

https://www.intra-mart.jp/download/product/iap/setup/iap_setup_guide/texts/install/linux/resin_linux.html

最新のResinresin-pro-4.0.xx.tar.gzを入手します。

[6] 7zipをダウンロード、インストール

tar.gz形式のファイルを展開するため、この記事では7zipを利用します。

sevenzip.osdn.jp

  1. resin-pro.4.0.xx.tar.gzを展開します
  2. resin-pro.4.0.xx.tarファイルが作成されます
  3. resin-pro.4.0.xx.tarを展開します
  4. resin-pro.4.0.xxフォルダが作成されます
  5. resin-pro.4.0.xxフォルダの直下に、automake, binといったフォルダが存在することを確認します

f:id:rinne_grid2_1:20180707183550p:plain

[7] Dockerプロジェクトのフォルダにresin-proをコピー

  • 上記の[6]の5のフォルダ「resin-pro.4.0.xx」の名称をresin-proに変更します
  • resin-proフォルダをim/apフォルダにコピーします

[8] プロジェクトのフォルダ構成の確認

  • フォルダを確認し、以下の構成と同じになっていることを確認します
  • ポイント
    • im/ap/resin-proフォルダがあり、直下にautomake等のファイルが存在する
    • im/ap/warフォルダがあり、imart.warファイルが存在する
im
│  .env
│  .gitignore
│  docker-compose.yml
│  README.md
│
└─ap
    │  Dockerfile
    │
    ├─resin-pro
    │  ├─automake  など
    │
    └─war
            imart.war

[9] 必要に応じて、設定ファイルを変更する

  • im/ap/resin-pro/conf/resin.properties の 82行目付近 - jvm_args

-Xmx, -Xmsの値が、初期状態だと8192m(8GB)が設定されているため、自分のPCのメモリ状況に合わせて変更します

jvm_args : -Dfile.encoding=UTF-8 -Djava.io.tmpdir=tmp -Xmx1500m -Xms1500m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=30 -XX:NewSize=512m -XX:MaxNewSize=512m -XX:+CMSClassUnloadingEnabled -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -Xloggc:log/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M
  • HTTPプロキシの設定 im/.env

社内ネットワーク等で、プロキシサーバーを経由する必要がある場合、.envのHTTP_PROXY、HTTPS_PROXYに値を設定します

HTTP_PROXY=http://user:password@server:port/
HTTPS_PROXY=http://user:password@server:port/

[10] Docker Machineを起動

  • Docker Machineを起動していない場合、下記コマンドで起動します
    • Docker Toolbeltという青いアイコンのショートカットを起動すると、自動でDocker Machineを起動してくれます

f:id:rinne_grid2_1:20180707183911p:plain

> docker-machine start

[11] Dockerコンテナの起動

  • プロジェクトフォルダに移動します
>cd any_folder\im
  • docker-composeを利用し、コンテナを起動します
> docker-compose up --build-d

ネットワーク接続環境にもよりますが、早くて5分、おそくとも30分程度で完了します

  • resinのindexページに接続します

http://192.168.99.100:8888

f:id:rinne_grid2_1:20180707184327p:plain

(Docker for WindowsやDocker for Macの場合は、上記IPアドレスではなくhttp://localhost:8888、もしくは自分で設定しているホスト名やIPアドレスにアクセスします。)

  • resinのページが開けることが確認できたら、warファイルをデプロイします
> docker exec im_ap /ap-server/bin/resinctl deploy /war/imart.war

コンテナ内のresin-proの場所は、/ap-serverです。 im/.envファイル内の変数を変更することで、お好きな場所を指定できます。

  • デプロイのコマンドが終了して、数分経ったらintra-martのセットアップページにアクセスします
    • 503 Service Temporarily Unavailableが発生する場合は、もう少しだけ待ってあげてください。

http://192.168.99.100:8888/imart/system/login

  • 無事にテナント設定画面が表示されるので、テナント環境セットアップを実行します

f:id:rinne_grid2_1:20180707184846p:plain

  • テナントIDはimartを指定します

f:id:rinne_grid2_1:20180707184958p:plain

  • リソース参照名は一覧に表示されたものを選択します

f:id:rinne_grid2_1:20180707185043p:plain

  • テナント登録を行い、しばらく待ちます

f:id:rinne_grid2_1:20180707185142p:plain

  • テナント環境セットアップが適切に動作しているかどうかについては、Adminerからテーブル作成状況を参照することで確認できます
情報名入力情報
データベース情報PostgreSQL
サーバdb
ユーザ名imart
パスワードimart
データベースimart
  • テーブルの作成状況が確認できます(だいたい500テーブルくらいができたら、処理完了です)

f:id:rinne_grid2_1:20180707185642p:plain

f:id:rinne_grid2_1:20180707185835p:plain

  • データベースやストレージ情報はDocker Volumeに保存しているため、データは永続化されています
    • 一度、docker-compose downで終了し、もう一度docker-compose upを試して、システムログイン画面にアクセスすると、ダッシュボードが表示されることがわかります

f:id:rinne_grid2_1:20180707191944p:plain

事象別のコマンドリファレンス

事象コマンド
Dockerコンテナを開始したいdocker-compose up
Dockerコンテナをビルドして開始させたいdocker-compose up --build
Dockerコンテナを終了させたいdocker-compose down
DBデータやストレージを削除して、
新しくテナント環境セットアップから始めたい(永続化しているコンテナのがデータ全部消えるので要注意)
docker-compose up
docker-compose down -v
docker-compose up
warファイルをアンデプロイしたいdocker exec im_ap /ap-server/bin/resinctl undeploy /war/imart.war
warファイルをデプロイしたいdocker exec im_ap /ap-server/bin/resinctl deploy /war/imart.war

コンテナごとの接続情報

  • APサーバー(CentOS)
コンテナ名ホスト名ポート番号(ホスト)ポート番号(コンテナ)
im_apap88888080
  • PostgreSQL
コンテナ名ホスト名DB名ユーザ名パスワードポート番号(ホスト)ポート番号(コンテナ)
im_dbdbimartimartimart54325432
  • Adminer
コンテナ名ホスト名ポート番号(ホスト)ポート番号(ゲスト)
im_apadminer88898080

まとめ

resin-proの設定とwarファイルさえ作成してしまえば、 あとは数コマンド叩くだけで、すぐに使えるintra-martの検証環境が手に入りました。 1時間かかっていた構築作業が、15分程度で済みそうです。

また、慣れていない人に利用してもらう場合も、3時間から30分程度に削減できそうです。

【雑記】インプットアウトプットという言葉の呪い

$
0
0

アウトプットしよう! 友達や会社の同僚に話してみよう!

よくみかける言葉である。

まず前提として、私は友達が少ない。 したがってブログでアウトプットする他ないだろう。

なんというか、簡単に内容を共有できる人がいたら、アウトプットの方法なんてそもそも調べない。 コミュ障、ぼっちの私だからこそ、そのようなキーワードでけんさくするんだ。

さて唐突に、少し話は変わる。 体の不具合にかんして調べると、 たいてい

「良質な睡眠をとりましょう」

「食生活に気を配りましょう」

といった内容がほとんどだ。 そして最後は、

「医者にいきましょう。」 である。

違うの!眠れないから体に不具合が発生することはわかってんの!

どうやって改善するかが知りたいの!

本当に求めている情報に出会うには、自分がその足で、専門家に聞いたり本を読んだりして、経験するしかないということだろうか。

さあ、そろそろ察したと思うが、私は国語が大の苦手である。文章を書くなんて全くできないし、 自分の本来の能力だけで、本の内容や経験したことを誰かに伝えるなんてことは到底不可能である。 (脳のスペックのもんだいである)

さて、ここでも得意の検索が始まるだろう。 国語読解力を高めるにはどうすればいいですか?

「本を読みましょ」

本の内容が思い出せない対策はどうしたらいいですか?

「アウトプットしましょ」

本の内容が理解できないのですが、どうしたらいいですか

「本をたくさん読みましょ」

さあ、迷路に迷い混んでしまったぞ。結局どうしたらいいのか?

・答え

自分の知恵になる感覚を、具体的に、正確に、情熱的に、教えてもらう 結局は他人に頼るしかないのだ

友達の少ない私でも、それは可能だろうか? 残り50年、もっと早くに力尽きるかもしれないが、他人に頼るのは怖い。 なにが怖いって、とても自分の思い通りにはできないし、絶対にそうはならないからだ。 必ず妥協が入る、評価が入る、会話キャッチボールで豪速球を投げられる。 豪速球でぶつけられる、もしくは落ちたものを拾いに行く、面倒。だから怖い

具体的に教えてもらわないとわからない病」は「具体的に説明しないと納得しないマンを相手に説明していると発症する

夢の中で誰かがそんなことをいっていた。

(支離滅裂な文章を書いているが、僕の脳内はきっと常にこんな感じである)

結論

このように、支離滅裂、もしくはテーマに沿って書いていない文ほど、自分本位であり迷惑なものはない。

かといって、テーマに沿っているように見えて、実は結局欲しい情報が得られない文章も同様に迷惑な話なのだ。


【体験レポート】メイン機をWindows10にアップグレードした

$
0
0

メイン機をようやくWindows10にアップグレードしたので、メモを残しておく。

基本的には、何事もなく無事にアップグレードできた。

個人的には、特に違和感なく使えている。

(サブ端末としてSurface Pro3を利用しており、 そちらはすでにWin10にアップグレード済みであるため)

目次

Windows10にして本当に大丈夫なのか

正直なところ、メインで使っているPCをWindows10にアップグレードするのは不安だった。

以下の記事で今年夏のWindows10向けアップデートで bash対応されるということがわかり、不安に思う中、アップグレードを決意した。

japanese.engadget.com

PC構成

  • 2010年4月にドスパラで購入したタワー型に、いくつかパーツ交換を実施
    • Prime Monarch ZF
    • OS: Windows8.1
    • マザーボード: ASRock X58 Extreme
    • CPU: Core i7-960
    • メモリ: 12GB(PC3-10600(2GB) × 6)
    • ストレージ: CT512MX100SSD1(C: 150, D:残)
    • グラフィックボード:Geforce GTX650 2GB
    • オーディオキャプチャ: UA-4FX(Roland)
    • TVチューナ: BUFFALO DT-H10 PCI

念のため最初にやったこと

  • システムイメージの作成
  • 回復ディスクの作成

アップグレード実施

右下に常駐しているWindowsマークから、「アップグレードを予約」する。

なお、僕の環境ではマークをクリックした後、 アップグレード予約のウィンドウが「応答なし」となってしまっていた。

しかし、5分~10分放置しておくと、「応答なし」が解除され、きちんとウィンドウ内容が表示されるので、 気長に待てばOKだと思われる。

予約時間になると「準備完了」というウィンドウが表示されるので、 表示内容に従って、作業を進めていく。

アップグレード結果

基本的に問題なし

ただし、地デジチューナー(DT-H10)が動作しなくなったため、一度アンインストールし、改めてインストールを実施

起動確認したソフトウェア(一通り問題なし)

  • RPGツクール2000、
  • RPGツクールXP
  • RPGツクールVX ace
  • RPGツクールMV
  • SONAR X1 Studio
  • Music Studio Producer
  • PyCharm4.5.4
  • VOCALOID3
  • GIMP2
  • GTA4(Grand Theft Auto Ⅳ)

なお、驚いたのはUA-4FXのドライバがWindows10経由で直接インストールされたことだ。 特に意識してドライバダウンロードし、インストールせずともデバイスを認識してくれた。

一部問題あり

  • PCast TV for 地デジ Lite (上記にも記載どおり、一度アンインストールし、再度インストール対応を実施)

初回はドライバ系が不安定になる

再起動もしくは一度電源を落として、改めて起動するとほぼ解決している。

【読書メモ】“内向型”のための雑談術 自分にムリせずラクに話せる51のルール

$
0
0

昔からずっと、雑談がうまくできない。 友人同士や職場の同僚同士なら、まだそれでも良かった。

しかし、最近はよくお客さん先に訪問するようになったため、 この「雑談できない事象」が自分自身の「問題」に変わった。

タイトルやレビューや確認していたのだが、 「内向型」のための雑談というあまり読んだことがない(個人的に)ようなタイトルであり、 自分の求めるものに合致していると感じて、購入を決めた。

“内向型”のための雑談術―自分にムリせずラクに話せる51のルール

ラインを引いたところ、印象に残った部分をメモする

読書メモ

雑談の悩み

  • 初対面の人と何をしゃべっていいのかわからないために、なかなか打ち解けられない
  • だれかと一緒にいると、沈黙をやぶる言葉が出てこなくて、いつも重苦しい空気になる
  • 大勢の人が集まるパーティや飲み会の席などでたいてい孤立してしまう

雑談は大切

  • 意味のないふざけた会話や人を笑わせるバカな話が、人と接する場面では大切な役割を果たす
  • 最初の雑談ができなければ、本当にいいたいことが伝えられない
  • 近づきすぎても、遠すぎても人付き合いはうまくいかない。その役割を果たしているのが「雑談」
  • 「小さな雑談」さえうまく使えれば、不思議と会話全体がうまくまわる

雑談の役割

楽しい話をすることでも、笑いをとることでもない
雑談のネタは、何も笑えるものでなく、身近なところに取り上げるだけで十分

雑談の目的

常に、雑談の目的を意識すべき。

  • 苦痛な状態からの回避
  • 場の空気を温める(相手の心を開きたい)
  • 好印象を残す(信頼を得ることで「次」につなげたい)

雑談の勘違い

  • 笑い話で盛り上げなければならない
  • たくさんしゃべらなければならない
    • 自分では喋らずに相手に喋ってもらう方が、雑談としての効果が高い
    • 「聞く雑談」に徹することでうまく切る抜けることができる
  • 豊富な話題をもっていなければならない
  • 普通の会話テクニックは内向型人間には使えない(そのとおり)
    • 感情を素直に表現する、明るい表情でからだ全体を使っておおげさにうなづく、自分から率先して会話に入る・・・等々、無理がある。

内向型人間の強み

  • 喋りが苦手である
    • 聞き役に徹することができる
  • 相手の気持ちに敏感である
    • 相手に迷惑がかかりそうになると、すぐにやめてしまう
  • しゃべる言葉を厳選している

感想等

「雑談の悩み」で記載した内容は、そのまま自分の悩みと言える。 こうったタイプの人間がどのように雑談に関する力を身につけていけば良いのか、そもそも身につけることができるのか、 様々な疑問が浮かんできており、本の内容がどう進むのか楽しみである。

【intra-mart】イントラマートの検証環境をDocker Composeで構築する(Oracle Database版)

$
0
0

以前イントラマートの検証環境構築に関して、以下の記事を書きました。

www.rinsymbol.net

上記のイントラマート環境はPostgreSQLを利用していました。
機能検証ならば、PostgreSQLでも十分なのですが、やはり実際に開発環境として利用する際には 運用環境で動いているDB(Oracle Database)とあわせるべきだと考え、Oracle Database版を作成しました。

Docker関係のプロジェクト配置先(Githubリポジトリ)

github.com

Dockerでやること

  • JDKのインストール
  • resin-proのセットアップ
    • CentOSのインストール
    • resinのmakeに必要なパッケージ・ライブラリのインストール
    • make実行
  • データベースのセットアップ
    • データベース作成
    • スキーマ、ユーザ作成
    • 権限付与

手動でやること

  • Oracle Database 12.2.0.1.0のダウンロードと対象フォルダへの配置
  • JDBCドライバ(ojdbc8.jar)のダウンロードと対象フォルダへの配置
  • resin-proのセットアップ
    • intra-martのダウンロードライブラリから、resin-pro-x.x.x.tar.gzをダウンロード&展開
    • プロパティの設定
  • Jugglingプロジェクトの作成、warファイル出力
  • warファイルのデプロイ
  • intra-martのテナント環境セットアップ

Dockerで作成するintra-martのシステム構成

Docker上の環境

ホスト名コンテナイメージ目的
apCentOS 7.5.1804(※1)(library/centos)resin-proの実行及びwarデプロイ
dbOracle Database 12.2.0.1.0(ビルドで作成)intra-martに関するデータの保存
adminerAdminer(library/adminer)dbのデータ参照用のアプリ

(※1 厳密には、NTTデータイントラマート社はintra-martが動作するLinux環境として、Red Hat Enterprise Linux 6.x、7.xのみを動作保証しているため、 本Docker関連ファイルを利用する場合は、あくまでも動作検証用の環境に留めておくことをおすすめします。本記事の内容によって発生した障害等について、一切責任を負いません)

必要なPC環境

  • 下記のいずれか
OSバージョンDocker
Windows7以降64bitDocker Toolbox
Windows1064bitDocker for Windows
macOS-Docker for Mac

事象別のコマンドリファレンス

事象コマンド
Dockerコンテナを開始したいdocker-compose up
Dockerコンテナをビルドしたいdocker-compose build --no-cache
Dockerコンテナを終了させたいdocker-compose down
DBデータやストレージを削除して、
新しくテナント環境セットアップから始めたい(永続化しているコンテナのデータが全部消えるので要注意)
docker-compose up
docker-compose down -v
docker-compose up
warファイルをアンデプロイしたいdocker-compose exec ap /ap-server/bin/resinctl undeploy imart
warファイルをデプロイしたいdocker-compose exec ap /ap-server/bin/resinctl deploy /war/imart.war
Oracleイメージを単独で作成したいdocker build -t oracle12c_local:latest ./db/build --build-arg DB_EDITION=SE2

コンテナごとの接続情報

  • APサーバー(CentOS)
コンテナ名ホスト名ポート番号(ホスト)ポート番号(コンテナ)
im_ap_nap88888080
  • Oracle Database
コンテナ名ホスト名DB名ユーザ名パスワードポート番号(ホスト)ポート番号(コンテナ)
im_db_ndbIMARTIMARTIMART152101521
  • Adminer
コンテナ名ホスト名ポート番号(ホスト)ポート番号(ゲスト)
im_adminer_nadminer88898080

利用手順の前に、よくあるエラー

  • ビルドに失敗する→im/db/buildの中のシェルスクリプト(sh)がCRLFになっている可能性があるので、LFにする
  • 表領域やスキーマ作成に失敗する→im/db/scripts_startupの中のシェルスクリプト(01_create_imart.sh)がCRLFになっている可能性があるので、LFにする

利用手順

本Dockerプロジェクトを利用して、intra-martの環境を構築する手順を記述します。 (この手順は、Windows10 + Docker Toolboxを前提にしています。必要に応じて適宜読み替えをお願いします。)

[1] Docker Toolboxのインストール

[2] Dockerのメモリ、ストレージを増やす

Oracle Databaseをインストールする際、空きディスク容量は15GB以上必要とのことです。

下記のコマンドで新たにdocker-machineを作ります。 この記事では、ディスクサイズ50GB、メモリは8GBにしました。 必要に応じて各数値は変更してください。

$ docker-machine create -d virtualbox --virtualbox-disk-size50000--virtualbox-memory"8192" default

[2] Gitのインストール

[3] Dockerプロジェクトのダウンロードと初期設定

  • 任意のフォルダで、以下のコマンドを実行し、dockerプロジェクトをダウンロードします
> git clone https://github.com/rinne-grid/docker-for-intra-mart-v-oracle12c-12.2.0.1.0 im
>cd im
  • warファイルの配置用フォルダを作成します
>mkdir .\ap\war

[4] Oracle Database 12.2.0.1.0をダウンロードします

[5] Jugglingでwarファイルを作成

プロジェクト名をimartにして、必要なモジュールを選択し、設定を行います。 今回のDocker環境をそのまま利用するためには、下記ファイルの設定を変更する必要があります

  • storage-config.xmlを設定する
  • resin-web.xmlを設定する
  • 出力するwarファイル名をimart.warとする

(Dockerプロジェクトのap/.envファイルを変更することで、別の値を指定することも可能です)

storage-config.xmlの設定

imart/config/storage-config.xml の 19行目付近を以下のとおりに変更します

<root-path-name>/im-data/storage</root-path-name>
resin-web.xmlの設定

imart/resin-web.xml 内容を下記のとおりにします

<web-app xmlns="http://caucho.com/ns/resin"xmlns:resin="urn:java:com.caucho.resin"><character-encoding>UTF-8</character-encoding><log-handler name=""class="jp.co.intra_mart.common.platform.log.handler.JDKLoggingOverIntramartLoggerHandler"/><logger name="debug.com.sun.portal"level="warning" /><!-- im_service(im_asynchronous) --><resource jndi-name="jca/work"type="jp.co.intra_mart.system.asynchronous.impl.executor.work.resin.ResinResourceAdapter" /><jsp><recycle-tags>false</recycle-tags></jsp><database jndi-name="jdbc/default"><driver><type>oracle.jdbc.driver.OracleDriver</type><url>jdbc:oracle:thin:@db:1521/RNGD</url><user>IMART</user><password>IMART</password></driver><max-connections>20</max-connections><prepared-statement-cache-size>0</prepared-statement-cache-size></database><session-config><reuse-session-id>false</reuse-session-id><session-timeout>30</session-timeout></session-config><mime-mapping extension=".json"mime-type="application/json"/></web-app>
warファイルの出力

imart.warという名称でwarファイルを出力したら、 プロジェクトのim/ap/warフォルダの中に、warファイルをコピーします

[6] intra-martのサイトからLinuxのresin-proをダウンロード

ライセンスキーを入力すると、ダウンロード可能なファイル一覧が表示されます。

なお、intra-martサイトにも書いているとおり、.tar.gzがLinux用のresin-proになります。

https://www.intra-mart.jp/download/product/iap/setup/iap_setup_guide/texts/install/linux/resin_linux.html

最新のResin (resin-pro-4.0.xx.tar.gz)を入手します。

[7] Dockerプロジェクトのフォルダにresin-proをコピー

  • 上記のresin-pro.4.0.xxを展開し「resin-pro.4.0.xx」の名称をresin-proに変更します
  • resin-proフォルダをim/apフォルダにコピーします

[8] JDBCドライバー(ojdbc8.jar)をダウンロードし、im/ap/resin-pro/libにコピー

[9] プロジェクトのフォルダ構成の確認

  • フォルダを確認し、以下の構成と同じになっていることを確認します
  • ポイント
    • im/ap/resin-proフォルダがあり、直下にautomakeやlibフォルダ等が存在する
    • im/ap/resin-pro/libフォルダ内にodbc8.jarファイルが存在する
    • im/ap/warフォルダがあり、imart.warファイルが存在する
    • im/db/buildフォルダ内にlinuxx64_12201_database.zipが存在する
im
├─ap
│  │
│  ├─resin-pro
│  │  ├─lib
│  │  │   ├─ojdbc8.jar
│  │  │   │
│  │  │   ├─activation.jar など
│  │  │          
│  │  ├─automake  など
│  │
│  ├─war
│  │  └─imart.war
│  │
│  └─Dockerfile
│
├─db
│  ├─build
│  │  ├─linuxx64_12201_database.zip
│  │  │
│  │  ├─checkDBStatus.sh など
│  │
│  ├─scripts_setup
│  │  └─01_create_imart.sh
│  │   
│  └─scripts_startup
│
├─.env
├─.gitignore
├─docker-compose.yml
└─README.md

[10] 必要に応じて、設定ファイルを変更する

  • im/ap/resin-pro/conf/resin.properties の 82行目付近 - jvm_args

-Xmx, -Xmsの値が、初期状態だと8192m(8GB)が設定されているため、自分のPCのメモリ状況に合わせて変更します

jvm_args : -Dfile.encoding=UTF-8 -Djava.io.tmpdir=tmp -Xmx4096m -Xms4096m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=30 -XX:NewSize=512m -XX:MaxNewSize=512m -XX:+CMSClassUnloadingEnabled -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -Xloggc:log/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M
  • HTTPプロキシの設定 im/.env

社内ネットワーク等で、プロキシサーバーを経由する必要がある場合、.envのHTTP_PROXY、HTTPS_PROXYに値を設定します

HTTP_PROXY=http://user:password@server:port/
HTTPS_PROXY=http://user:password@server:port/

[11] Docker Machineを起動

  • Docker Machineを起動していない場合、下記コマンドで起動します
    • Docker Toolbeltという青いアイコンのショートカットを起動すると、自動でDocker Machineを起動してくれます

docker-toolbelt

> docker-machine start

[12] Dockerコンテナの起動

  • プロジェクトフォルダに移動します
>cd any_folder\im
  • docker-composeを利用し、コンテナを起動します
> $ docker-compose up -d ; docker-compose logs -f

イメージ作成の際にアップデート等の処理が走るので、30分程度~1時間程度かかります。

terminalにいろいろと出力されていきます。 以下の表示が出たら終了と判断して良いと思います。Ctrl+C等でを強制終了します。

db_1       | #########################
db_1       | DATABASE IS READY TO USE!
db_1       | #########################
db_1       | The following output is now a tail of the alert.log:
db_1       | EXTENT MANAGEMENT LOCAL AUTOALLOCATE
db_1       | SEGMENT SPACE MANAGEMENT AUTO
db_1       | 2019-03-07T14:11:29.343682+00:00
db_1       | RNGD(3):Completed: CREATE TABLESPACE IMART
db_1       | DATAFILE '/opt/oracle/oradata/ORCL/XXXX/IMART.dbf' SIZE 7000M REUSE
db_1       | LOGGING
db_1       | ONLINE
db_1       | BLOCKSIZE 8K
db_1       | EXTENT MANAGEMENT LOCAL AUTOALLOCATE

ホストマシン側からOracle Databaseに接続したい場合は下記の指定を行います。

(設定を変更していない場合)

ユーザIDパスワードSID
IMARTIMART192.168.99.100:15210/RNGD

(Docker for WindowsやDocker for Macの場合は、上記IPアドレスではなく localhost:15210 もしくは自分で設定しているホスト名やIPアドレスを指定します。)

docker-toolbelt

(Docker for WindowsやDocker for Macの場合は、上記IPアドレスではなく http://localhost:8888もしくは自分で設定しているホスト名やIPアドレスにアクセスします。)

  • resinのページが開けることが確認できたら、warファイルをデプロイします
> docker-compose exec ap /ap-server/bin/resinctl deploy /war/imart.war

コンテナ内のresin-proの場所は、/ap-serverです。 im/.envファイル内の変数を変更することで、お好きな場所を指定できます。

  • デプロイのコマンドが終了して、数分経ったらintra-martのセットアップページにアクセスします

    • 503 Service Temporarily Unavailableが発生する場合は、もう少しだけ待ってあげてください。

    http://192.168.99.100:8888/imart/system/login

  • 無事にテナント設定画面が表示されるので、テナント環境セットアップを実行します

tenant1

  • テナントIDはimartを指定します

tenant2 * リソース参照名は一覧に表示されたものを選択します

tenant3

  • テナント登録を行い、しばらく待ちます

tenant4

  • テナント環境セットアップが適切に動作しているかについて、ログを確認したい場合以下のコマンドを実行します
$ docker-compose exec ap bash
$ less -f ./log/jvm-app-0.log
  • データベースやストレージ情報はDocker Volumeに保存しているため、データは永続化されています
    • 一度、docker-compose downで終了し、もう一度docker-compose upを試して、システムログイン画面にアクセスすると、ダッシュボードが表示されることがわかります

dashboard

Oracle DatabaseやJDBCドライバのダウンロードなど 初回Dockerイメージ作成時はそれなりに時間がかかります。

しかし、一度Dockerイメージさえできてしまえば、すぐに新たな環境で 開発を始めることができるため、非常に便利だと思いました。

数ヶ月前から、Oracle版を導入したいと考えてたのですが、 Oracle DatabaseのDockerイメージのビルドがうまくいかず、ドはまりして諦めかけていましたが なんとか完了させることができて良かったです。

【Linux】Linuxカーネルをx86_64向けにビルドしてQEMUで実行するまでの記録

$
0
0

なぜカーネルのビルド・実行を体験したかったのか(ポエム)

  • そもそものコンピュータ・アーキテクチャ、ソフトウェア工学的な知識がなく、今後応用が効かないのではないかという危惧
  • (個人的に)ブラックボックス化の上に成り立つ仕組みの栄枯盛衰をいくつか見て、無常を感じた
    • ブラックボックスなレイヤはほぼ変わっていない
    • 10年後に別のものに変わらないとは必ずしも言い切れないけど、ほぼ必ず存在すると思われる
  • こんなことを考えていると、低レイヤの仕組みについて知りたい欲が抑えきれない
    • 自分の中で完全にブラックボックスになっている部分に手を出したい
    • しかし挙動や全体像が不明のままソースを読んでも迷子になって挫折するだけ
  • 取っ掛かりとして、実際に手元で動かすことができるカーネル環境を試す

目次

今回試したカーネルバージョン

  • linux-4.19.63

ビルド環境

実行環境・ツールなど

  • QEMU 2.11(apt-get経由)
  • BusyBox 1.31.0

パッケージのインストール

$ sudo apt update -y
$ sudo apt upgrade -y
$ sudo apt install -y git build-essential ncurses-dev bison flex libelf-dev openssl libssl-dev 
$ sudo apt-get install -y qemu
# 下記は必要に応じて
$ sudo apt-get install -y bzip2 vim wget

Linuxカーネル取得

# clone完了まで30分~1時間程度
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
$ cd ./linux
$ git tag|grep v4.19.63
$ git checkout -b v4.19.63 v4.19.63

Linuxカーネルのビルド

  • 今回はx86_64を対象としてビルドする
# x86_64の設定ファイルを配置する
$ cp arch/x86/configs/x86_64_defconfig ./.config
$ # .configをもとに関連ファイルを設定する
$ # 有効にすべき機能の取捨選択を判断できる知見を持っていないので、今回はEnter押しっぱなしにした
$ make oldconfig
$ make
  • arch/x86_64/boot/bzImageが作成されている

qemuの-initrdオプションに指定するイメージを作成

busyboxのソースをダウンロード

$ wget https://busybox.net/downloads/busybox-1.31.0.tar.bz2
$ tar -jxvf ./busybox-1.31.0.tar.bz2
$ cd ./busybox-1.31.0

busyboxのビルド (make menuconfig)

  • ポイント
    • Setting -> Build static binary (no shared libs)をチェックする
    • Networking Utilities -> Enable IPv6 Supportのチェックを外す
$ make menuconfig
# Setting -> Build static binary (no shared libs)をチェックする# Networking Utilities -> Enable IPv6 Supportのチェックを外す

$ make install

busyboxディレクトリに_installディレクトリが作成されている

dev, dev/null, proc, sys, etc/init.dを作成

$ cd ./_install
$ mkdir dev
$ sudo mknod dev/null c 13
$ mkdir proc
$ mkdir sys
$ mkdir-p etc/init.d

_install/etc/init.d/rcSを作成

$ vim ./etc/init.d/rcS

# 以下の内容を記述#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s

_install/etc/inittabを作成

$ vim ./etc/inittab
# 以下の内容を記述

::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::respawn:/sbin/getty -L ttyS5 115200 vt100
::ctrlaltdel:/bin/umount -a-r

初期化プロセスのプログラムを作成(_install/init.c)

  • ここではbusyboxの_installディレクトリ直下で作業を実施
/* init.c */#include <stdio.h>void main() {
    printf("Hello World!");
    while(1);
}
  • init.cをコンパイル(_install/init)
$ gcc -static-o init ./init.c

パーミッションの変更

$ chmod755 ./init ./etc/init.d/rcS

_installディレクトリ以下でコマンドを実行し、cpio形式イメージを作成

$ find . | cpio -o--format=newc | gzip > ../initrd.img

ビルドしたカーネルをqemuで起動

$ qemu-system-x86_64 -m1024-nographic-kernel ~/linux/arch/x86_64/boot/bzImage  -initrd ~/linux/busybox-1.31.0/initrd.img -append"console=ttyS0 root=/dev/mem0 rdinit=/sbin/init"
  • 正しくビルド、起動できている!

f:id:rinne_grid2_1:20190812183259p:plain
kernel4.19.63

今後どうしたいのか

  • (個人的)ブラックボックスを少しずつ減らしたい
  • デバッグしてみたい

参考

busyboxの設定やデバイスファイル生成など、下記のサイト様を参考にしました。

blog.hirokikana.com

【雑記】エクスプレス予約で新幹線の乗り換えをしようとした時にミスったのでメモ

$
0
0

ここ1ヶ月くらいの話なのですが、よく西日本に出張に行くようになりました。

飛行機が使えればベストなのですが、やはりコストがかかるということもあり、新幹線の利用が(僕の勤め先の)出張の常識のようです。

博多までだと、新幹線で5時間程度かかるので、非常に長いですね。

さて、僕の勤め先では仕事で出張予定が入った時、「エクスプレス予約」サービスで新幹線を予約します。 エクスプレス予約サービスを契約すると、専用のICカードが発行され、手元に届きます。これを新幹線乗車時に利用するのです。

(年会費が1,000円かかるらしく、個人ではちょっともったいなくて利用しないです。)

エクスプレス予約とは

お盆や年末年始、連休などの期間も含め、1年中いつでもおトクに東海道・山陽新幹線の指定席を利用できる会員制のネット予約サービスです。ご利用にあたっては年会費1,000円(税抜)がかかります。

expy.jp

予約時の流れはこんな感じです

  • Webもしくはスマホアプリで新幹線を予約(座席指定可能)
  • 当日に改札で専用のICカードをかざす
  • 改札の反対側の排出口(?)から、乗車予定の新幹線情報(座席、時間など)が記載されたチケットが出る

利用開始時には「こんなに簡単なのか!」と驚きました。

乗り換え時のトラップ

僕は品川駅から新幹線に乗換えるのですが 乗り換え改札でエクスプレスICカードをタッチするも…うまく通れなかったのです。

切符と同じ考え方*1なのかと思い、 エクスプレスICカードをタッチしたあと、PASMOをタッチしてもだめ!!

一瞬、予約できていなかったのではないかと絶望しかけましたが、駅員さんが助けてくれました。

どうすべきだったのか。

PASMO(等の交通系カード)とエクスプレスICカードを重ねて一緒に、タッチする必要があるとのこと

そんなん知らんかったよ…。

普段、定期入れにPASMOを入れっぱなしにして、改札でそのままタッチしている僕としては、 大きなトラップでした。

次回からは、乗り換えの時に関しては、PASMO(等の交通系カード)とエクスプレスICを重ねてタッチするように気をつけます。

*1:新幹線切符で乗り換える場合は、切符を投入したあとにPASMO等の交通系カードをタッチする

Viewing all 242 articles
Browse latest View live