Hatena::ブログ(Diary)

風柳メモ このページをアンテナに追加 RSSフィード Twitter

2011-11-01

11月1日

この一年で書いた記事の少なさ(41件)に軽くショックを受けたり受けなかったり。

いや、ココログの方はさらに少ない訳だけれども…もはや手がURLを忘れかけているレベル。

2011-09-23

GAE-Cronをバージョンアップしました(version 0.02a)

GAE-Cronのバージョンアップの前に:Google App Engineの設定変更のススメ

GAEが新規料金体系になるのに伴い、Free Quotaの範囲で使えるリソースが大幅に制限されてしまうため、GAE-Cronのようなソフトはもろに影響を受けてしまいます(哀)。

今回のversion 0.02a(一応CPU負荷低減版)で、200個の1分間隔タイマをかけた状態でBilling Historyを見てみると、現在の料金体系だと

ResourceUsedFreeBillableCharge
CPU Time
$0.10/CPU hour
0.986.500.00$0.00
Total $0.00

のように余裕でFreeの範囲で収まっていた(Free Quotaの約15%)ものが、新料金体系だと、

ResourceUsedFreeBillableCharge
Frontend Instance Hours
$0.04/Hour
159.5228.00131.52$5.43
Total $5.43

のように、いきなりはねあがって(Free Quotaの約570%)しまいます。

どうしようもないかと思っておりましたが、一応、管理画面の

Administration > Application Settings > Performance > Max Idle Instances

で、デフォルトでは“Automatic”になっているところを、“1”に下げると、

ResourceUsedFreeBillableCharge
Frontend Instance Hours
$0.04/Hour
26.6028.000.00$0.00
Total $0.00

と、ぎりぎりFree Quotaにおさまる所まで抑えられました(パフォーマンスは落ちているんだとは思いますが)。

Freeで使っている方は、とりあえず、上記設定でお試し下さい。

それでもFree Quotaを越えるようであれば、当方では対処は困難です。悪しからずご了承下さい。


CPU負荷低減を目指して…いたんです、けど、ねぇ

http://gaecronclub.appspot.com/

GAE-Cronの version 0.02a をアップしました。

ダウンロード(version:0.02a):gaecron-v002a.zip

課金の対象がこれまでのCPU HoursからInstance Hoursに変更になってしまうので、CPU負荷を減らす試みは無駄になったかもしれない…。

アップデート方法

■ファイルの更新

基本、全てのファイルを上書きして下さい。

その際、*.yamlなどの設定ファイルや、*.py内のPATH等の書き換えなどを行なっている場合は、該当箇所の書き換えも忘れないようにして下さい。

■全タイマの再設定

管理者権限でログインし、画面左上部にある[全タイマ再設定]ボタンを押して下さい。

version 2.0→2.0aの移行に限り、もしかすると、再設定は不要になっている、かも知れません。うまくいかないときには、再設定して下さい。

再設定すると、全てのタイマがかけなおしとなるため、特に長周期のタイマを設定している人がいると影響が大きいと思われますが、仕様ということで悪しからず。

覚え書きなど

CPU負荷低減の仕組みは、この記事の応用。

GAEでPython 2.7が使えるようになったら(2011/12/1?)、スレッド化すればもう少し効率はよくなりそうだけれど……。

ちょっと時間が取れないため、今のところは対応予定なし。

2011-06-19

JavaScriptでプロパティの継承が出来るクラスっぽいものを実装する試み

同じような/より良い実装は星の数ほどあると思うけれども、個人的な覚書を兼ねて。

クラス(関数)作成用関数

var makeClass=(function(){
    var origin_constructor=null;
    var name_to_class={};
    var change_toString=function(target,name){
        var _toString=target.toString;
        target.toString=function(){
            return _toString.apply(this,arguments).replace(/\x28/,name+'\x28');
        };
    };
    var update_properties=function(obj_constructor,prop_assoc){
        var _prototype=obj_constructor.prototype;
        for (var key in prop_assoc) {
            if (!prop_assoc.hasOwnProperty(key)) continue;
            _prototype[key]=prop_assoc[key];
        }
    };
    var makeClass=function(base,prop_assoc,name){
        if (!prop_assoc) prop_assoc={};
        var obj_constructor=function(){};
        obj_constructor.__for_class__=true;
        var super_constructor=null;
        if (base) {
            switch (typeof base) {
                case    'string'    : if (name_to_class[base]) super_constructor=name_to_class[base].__constructor__; break;
                case    'function'  : super_constructor=(base.__constructor__)?base.__constructor__:base; break;
                case    'object'    : super_constructor=base.constructor; break;
            }
            if (super_constructor&&super_constructor.__for_class__) {
                try{
                    obj_constructor.prototype=new super_constructor();
                    obj_constructor.prototype.__super__=super_constructor;
                }
                catch (e){
                    super_constructor=null;
                }
            }
        }
        if (!super_constructor&&origin_constructor) {
            obj_constructor.prototype=new origin_constructor();
            obj_constructor.prototype.__super__=origin_constructor;
        }
        update_properties(obj_constructor,prop_assoc);
        obj_constructor.prototype.constructor=obj_constructor;
        
        var __class__=function(){
            var self=new obj_constructor();
            self.__class__=__class__;
            if (typeof self.__init__=='function') self.__init__.apply(self,arguments);
            return self;
        };
        obj_constructor.__class__=__class__;
        __class__.__constructor__=obj_constructor;
        
        __class__.update_properties=function(prop_assoc){
            update_properties(obj_constructor,prop_assoc);
        };
        
        if (typeof name=='string') {
            __class__.__name__=obj_constructor.__name__=name;
            change_toString(__class__,name);
            change_toString(obj_constructor,name);
            name_to_class[name]=__class__;
        }
        return __class__;
    };
    origin_constructor=makeClass(null,{},'Class').__constructor__;
    
    return makeClass;
})();   //  end of makeClass()

使い方

/* 【クラス(関数)作成】 */

var ClassFoo = makeClass( base, prop_assoc, name );

// base: 継承元クラス(無い場合はnull)

// prop_assoc: プロパティ定義用連想配列(__init__を定義しておくと、オブジェクト作成時にコールされる)

// name: 一意なクラス名


/* 【オブジェクト作成】 */

var ObjectFoo = ClassFoo( args );

// args: __init__プロパティ(メソッド)に渡す引数


/* 【プロパティ(メソッド)コール】 */

ObjectFoo.prop( prop_args );

// prop: クラス作成時に定義したプロパティ(メソッド)名

// prop_args: プロパティ(メソッド)に渡す引数


/* 【クラスのプロパティ追加・更新】 */

ClassFoo.update_properties( prop_assoc );

// prop_assoc: プロパティ定義用連想配列



使用例
/* 【クラス(関数)作成】 */
var ClassA=makeClass(null,{
    __init__ : function(a){ // __init__はオブジェクト作成時にコールされる
        var self=this;
        self.a=a;
    }

,   alert : function(name){ // プロパティ名 : 関数の形で定義
        var self=this;
        alert(self[name]);
    }
},'ClassA');

var ClassB=makeClass(ClassA,{ // ClassAを継承
    __init__ : function(a,b){ // ClassAの__init__を上書き
        var self=this;
        self.a=a;
        self.b=b;
    }

    // ※ alertプロパティは未定義→ClassAのものが参照される

,   sum : function(){ // 新規プロパティ
        var self=this;
        alert(self.a+'+'+self.b+'='+(self.a+self.b));
    }
},'ClassB');

/* 【オブジェクト作成】 */
var ObjectA=ClassA(1,2), ObjectB=Class(3,4);

/* 【プロパティ(メソッド)コール】 */
// ObjectA
ObjectA.alert('a'); // 1
ObjectA.alert('b'); // undefined
//ObjectA.sum(); // [Error] ObjectA.sum is not a function

// ObjectB
ObjectB.alert('a'); // 3
ObjectB.alert('b'); // 4
ObjectB.sum(); // 3+4=7

/* 【クラスのプロパティ追加・更新】 */
ClassA.update_properties({
    alert : function(name){ // alertを上書き
        var self=this;
        alert(name+' => '+self[name]);
    }

,   test : function(){ // 新規プロパティ(ClassBにも無し)
        alert('test()');
    }

,   sum :   function(){ // 新規プロパティ(ClassBに同名のプロパティ有り)
        alert('sum() is not supported');
    }
});

/* 【ClassA更新後のプロパティ(メソッド)コール】 */
// ObjectA
ObjectA.alert('a'); // a => 1
ObjectA.test(); // test()
ObjectA.sum(); // sum() is not supported

// ObjectB
ObjectB.alert('a'); // a => 3
ObjectB.test(); // test()
ObjectB.sum(); // 3+4=7

仕様上の注意
  • makeClass()の第1引数:継承元クラスとして指定可能なのは、null(継承無し)、および、makeClass()によって作成されたクラス(関数)(これは既存クラスから作成したオブジェクトや既存クラス作成時に指定した一意なクラス名でも代用可)に限る(例えばObjectやArrayその他一般のオブジェクト、リテラル等は不可)。
  • makeClass()の第3引数:一意のクラス名は、実は省略可(ただし省略すると、第1引数のクラス名(文字列)指定が不可になったり、クラス(関数)やオブジェクトのconstructorを(toString()して)見た時に解り難くなるなど、便宜上いくつか問題が出るかも知れないので注意)。
  • クラス(関数)作成時に__init__プロパティ(メソッド)を定義しておけば、クラス(関数)を呼出してオブジェクトを作成する時(作成処理の最後)にこれがコールされる。その際、クラス(関数)で指定した引数がそのまま渡される。
  • プロパティは、同名のものがある場合、オブジェクト自身のプロパティ>クラスのプロパティ定義>継承元クラスのプロパティ定義の優先順位で参照される。
  • クラスのプロパティをupdate_properties()等で追加・更新した場合、その影響は当該クラスから直接作成されたオブジェクトの他、当該クラスを継承した下位クラス及びそれから作成されたオブジェクトにも及ぶ(ただし、優先順位の関係で、下位クラスやオブジェクトが、自身のものとして同名のプロパティを持つ場合は影響を受けない)。
  • 下位クラス及びそれから作成されたオブジェクトに対するプロパティの追加・更新は、上位(継承元)クラス及びそれから作成されたオブジェクトには影響を及ぼさない。


ところで

new無しでも有りでもオブジェクトが作成できるコンストラクタは、

var Const=function(){
    if (!(this instanceof Const)) return new Const();
};
Const.prototype.test=function(){
    alert('OK');
};

new Const().test(); // OK
Const().test(); // OK

みたいな感じで作れるけれども、コンストラクタに任意の引数を指定したい場合はどうすれば良いのだろう?

var Const=function(){
    if (!(this instanceof Const)) return new Const(); // 【???ここをどう書くかが思い付かない???】
    this.__init__.apply(this,arguments);
};
Const.prototype.__init__=function(){
    alert(arguments.length);
};

new Const(1,2,3); // 3
Const(1,2,3); // 0 [NG]

とりあえず、上の方では、

var Const=function(){};
Const.prototype.__init__=function(){
    alert(arguments.length);
};

var Class=function(){
    var self=new Const();
    self.__init__.apply(self,arguments);
    return self;
};

new Class(1,2,3); // 3
Class(1,2,3); // 3

のようにして関数を一つかます形で実装しているけれども。

2011-06-05

Jコミのビューワーでルーペ機能が復活しないかな?

【 3:PDF化にまた難関 】

<中略>

作者がPDF化を希望しない場合、読者には素直にPDF版を諦めていただく。


広告主が作品に興味を示さなかった場合と、作者が広告枠の金額に納得しなかった場合も、PDF化は不可能。

という感じになるかと思います。

(23) Jコミ ★ 5月のまとめ - (株)Jコミの中の人

PDF化にさまざまな難関があるということで……まぁそれはいいのですが(個人の感想です)*1、それならそれで、公式ビューワー(アルヌール)で以前搭載されていたルーペ機能が復活して欲しいな〜と。


把握している範囲では、典型的なアルヌールの画像サイズは4種類(現行3種類)あって、

種類画像サイズ(横×縦・単位:ピクセル)備考
高解像度827×1170拡大(ルーペ)機能で使われていたもの(PDFの原版?)
中解像度595×842ビューワー 大画面版
低解像度420×595ビューワー 通常版
フリーサイズ720×864(一例)ビューワー フリーサイズ版(例:あんたっちゃぶる

普通に見るぶんには大画面版ビューワー(中解像度)で充分だとは思いますが、ときどき、手書きで小さく書かれている文字等が潰れてしまって見難い場合があります。

そんなとき、ルーペ機能があればいいのになーと思う次第。

常にPDF化がなされるなら、いずれそっちで確認可能だったんですけれどね…。

*1スマートフォンiPadKindleといったハイカラな(笑)機器は所持しておらず、もっぱらPCで閲覧なので。

2011-06-04

Google Chromeのbackground_pageで一定長以上の文字列を扱うとクラッシュすることがある

Google Chromeで、拡張機能の再読み込みや(ブラウザの)再起動を繰り返していたら、突然特定の拡張機能がクラッシュするようになり、以降は当該拡張機能は再読み込みやChromeの再起動を実施(タスクマネージャにて、いったんchromeがすべてなくなっているのは確認済)しても必ずクラッシュしてしまい、Windowsを再起動するしか無くなる、という現象に悩まされていましたいます。

とりあえず、Chrome 10.0.648.205/Windows XP SP3で現象の発生を確認。

調べてみたところ、

  • background_pageにて一定長以上の文字列を扱うと、クラッシュする状態

になっているらしい、ということが解りました。

上記現象が発生した状態で、background_pageに

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
for (var ci=0,s=[]; ci<569280; ci++) s[ci]='A'; s=s.join(''); alert(s);	// OK
for (var ci=0,s=[]; ci<569281; ci++) s[ci]='A'; s=s.join(''); alert(s);	// NG(crush)
</script>
</head>
<body>
</body>
</html>

のように記述した拡張機能を起動すると、2個目のalert(s)でクラッシュしてしまいます(try{}catch(e){}でも捕捉出来ません)。

ちなみに、alert(s.length)は動作します。

569281という数字がマジックナンバーなのか(常に同じ数字になるか)どうかまでは不明。

【2011/06/04追記】その後発生したときには、233409でクラッシュしたので、状況により変化する模様。

例えばAutoPatchWorkの場合だと、background_pageとなっているindex.htmlから呼び出すStorage.js内のStorage.has()で

		has:function(key){
			if (localStorage[key] === void 0) {
				return false;
			}


のようになっていますが、keyが'siteinfo_wedata'で、localStorageに中身(siteinfoを文字列化したもの:2010/06/04時点で700,000文字以上)がある場合、localStorage[key]を評価した時点でクラッシュしてしまいます。

同様に、Storage.set()で

			localStorage.setItem(key, JSON.stringify(data));

のようにしていますが、'siteinfo_wedata'だと、JSON.stringfy()した結果を設定しようとした段階で、クラッシュします。

また、Storage.get()では、

					localStorage.removeItem(key);

のような処理がありますが、keyに対応する内容が一定サイズ以上の文字列の場合、localStorage.removeItem()をコールすると、他の拡張(localStorageを使っている/いないに関わらない)を巻き込んでクラッシュしてしまうこともあるようです。

では、どうすればよいか?

今のところ(OSを再起動する以外に)確実に回避もしくは復旧する方法は掴めていません。

ただ、いろいろと試した後、

  • アドレスバーからjavascript:(function(c,s){for(;c<569281;c++)s[c]='A';s=s.join('');alert(s)})(0,[])としても、クラッシュしなかった*1
  • 上記を実行してから、当該拡張機能を再読み込みしたところ、正常に起動した。

ということで、もしかすると、

javascript:(function(c,s){for(;c<1000000;c++)s[c]='A';s=s.join('');if(s){}})(0,[])

のようなブックマークレットを登録しておけば、いざというときに緊急回避出来るかも知れません。

とりあえず、再現待ちかな〜。

【2011/06/04追記】

その後同現象が発生したときに上記ブックマックレットを実行してみたところ、文字長制限は緩和されたように思えたが、別の要因で(?)クラッシュするようになってしまった模様。結局OS再起動するしかなさげ。詳細な原因は追えていない。

Jコミと私

所信表明的なものはあまり得意でないのですが

朴さんよりコメントを頂いたこともあり、ちょうどよいので書いておこうと思います。

「Jコミのビューア画面で自動ページングさせるスクリプト」公開の経緯

Jコミがこういった対策をとっているのは、あなたが公開されているスクリプトは作者の利益にならないからではないでしょうか?

朴さんからのコメント

そうですね、実はJコミのビューア画面で自動ページングさせるスクリプト作成時にはその点でおそらく問題とされるだろう認識はあり、公開に対するためらいも一応あったのですが、

  • ヘビーな漫画読みにとっては苦痛を感じるレベルでページ繰りが遅い(キャッシュ機能追加で若干改善されましたが、まだまだ…)。
  • 単に『パフォーマンスを改善して』という要望だけでは弱いので、具体的に(同じJavaScriptを使って)ここまでストレスが軽減出来るという例を示し、公式ビューアが少なくともそのレベルまで改善されることを期待する。

という理由もあって、公開してきました。

ですが、マイナス面の効果しか得られなかったということは、方策が間違っていたということでしょう。そんなわけで、今回で以降の更新は一応しない方向とすることにしました


立場

誤解しないで欲しいのですが、私自身はJコミを応援している、いちユーザです*2

ただひとえに、ユーザにとっての基本的な機能の一つである、閲覧環境が少しでも改善されて欲しいだけなのです。


ここからは蛇足になってしまいますが……。


私自身は比較的ヘビーな漫画読みです。


Jコミのおかげでこれまでよく知らなかった作家の方の作品も知ることができ、作風が好みだった作家の方については単行本を購入するきっかけになったりもしていますので、感謝しています(その意味では、アルヌールに追加予定の自動アフィリエイト作成機能や、作品データの新設は利便性の向上ということで歓迎でした)。


また、自分は昔から、基本『新刊が手に入るものは新刊しか買わない』主義です(ちなみに、ひと月の新刊コミックス購入代金は6〜7万円程度)。

これは、少しでも作者の利益と意欲に繋がって欲しいという思いからでしたが、その意味でJコミの方針には大いに共感するところです。


ただ、興味の範囲が作家の関連作品までなので、その範疇に入らない広告には無関心なため、いまのところ広告主のプラスにはなっておりませんが(苦笑)。

ネット広告というのは「偶然見て、興味が出たらクリックする」のが本筋

(23) Jコミ ★ 5月のまとめ - (株)Jコミの中の人

その通りです、興味を引かれないのでクリックしていません……すみません。

自動アフィリエイト機能が追加されたら、喜んでクリックするようになると思いますので、ぜひとも宜しくお願いいたします。

*1:ちなみに、alert()の制限なのか、724961文字以上だと実行できずに「ウェブページの表示中に問題が発生しました」となってしまいます→【2011/06/04追記】その後発生した際には、233441文字で実行出来なくなりました。これも状況によって変化する模様。またこのときにはbackground_pageは233409文字でクラッシュしたが、数字の近さに意味があるのかどうかは不明。

*2:そうでもなければわざわざ不具合報告(12)とかしませんし、Jコミで著作者をGoogle/Amazonで検索するリンクをつけるスクリプトとか、Jコミ新着案内(非公式)なんて作りません。