タスクの動的実行

通常Javascriptでは、読み込むJavascriptのファイルをHTML文書内のscriptタグに記述し静的に呼び出す。
しかし、複数のタスクを組み合わせて動作させるようなユースケースでは、HTMLの読み込み時に利用するタスクが確定していないことが多いため、この呼び出し方法だけでは都合が悪い。
このように、時間軸に対して動的にJavascriptのファイルを読み込みたいことがあるが、動的に読み込む方法としては次の4通りある。

  1. PHP等のサーバサイドスクリプトでscriptタグを動的に生成する。
  2. クライアントサイドJavascriptでdocument.write()を用いてscriptタグを動的に生成する。
  3. クライアントサイドJavascriptでscript elementをdocument.createElement()を用いて生成する。
  4. Ajaxを用いてJavascript fileを読み込み、その中身をeval()関数で実行する。

どれも長所と短所があり、使いわける必要がある。

まず、1番の方法は従来型Webシステムでよく使われる方法である。下記のようにRubyやPHP等のサーバサイド言語で記述される。

<?php foreach($mods as $k=>&$v){ ?>
<script type="text/javascript" src="<?php echo $k; ?>"></script>
<?php } >

この方法ではJavascript読み込みの際にページ遷移が必要となるが、複数のタスクが並列して動作するシステムにおいては、ページ遷移を伴なわない実装が望ましいため、この方法はページを初期化する際を除いて望ましくない方法である。また、これはサーバサイドでの処理となるため、クライアントサイドで独立して動作させたい場合には都合がよくない。

2番目、3番目は、クライアント側のjavascriptにて動的にscriptタグを生成して、javascriptファイルを読む込む方法である。両者の違いはwindow.onloadの呼び出し前か後で使い分ける点のみである。どちらも、ブラウザのネイティブコードにて構文解釈されるため実行速度は高速である。下記のような実装になる。

SysU={
    jspaths_:{},
    systemout:function(s){document.write(s);},
    executeJs:function(code,sync){
        if(sync){
            try{eval(code);return true;}
            catch(e){return false;}
        }else if(this.header){
            this.header.appendChild(document.createTextNode("\n"));
            var sc=document.createElement("script");
            sc.type="text/javascript";
            try{sc.innerHTML=code;}catch(e){sc.text=code;}
            this.header.appendChild(sc);
        }else{
            this.systemout('<script type="text\/javascript">'+code+'<\/script>');
        }
        return true;
    },
    importJs:function(jspath){
        if(this.jspaths_[jspath]){return false;}
        if(this.header){
            this.header.appendChild(document.createTextNode("\n"));
            this.header.appendChild(
                this.extend(document.createElement("script"),
                {type:"text/javascript",src:jspath}));
        }else{
            this.systemout('<script type="text\/javascript" src="'+jspath+'"><\/script>');
        }
        this.jspaths_[jspath]=true;
        return true;
    },...
};

これは便利な方法ではあるが、複数のJavascriptファイルを読み込む際にファイルの読み込む順番が保証されないし、また、Javascriptファイルの読み込みを完了したことを知る手段が無いという欠点がある。読み込み順序が保証されないため、ターゲットのファイル内では関数とクラスとコンポーネントの定義のみにとどめるべきなど実装上の制約がいくつか存在する。
また、Javascriptファイルの読み込みを完了したことを知るため、ターゲットのファイルの最終行に、読み込み完了イベントを通知する仕組みを用意する必要もある。

例えば、ターゲットファイルの末尾に以下のようなコードを埋め込むことである。

...
Sys.send({'type':'eof'});

なお、TeaOSではTask定義関数ys.registerExecutable()を実行した際に終了通知イベントが発行される。

Sys.registerExecutable({	...});

この2番目、3番目の方法の欠点は、同期保証するために、ターゲットファイル(読み込むファイル)に加工する必要がある点である。ライブラリなど手を加えることが望ましくないコードの場合には、この方法は使えない。

4番目の方法、Ajaxを用いてJavascript fileを読み込み、その中身をeval()関数で実行するという方法は、2、3番目の方法と異なり、同期保証も、読み込み終了検知もできるが、実行速度は非常に遅い。また、DOMベースのデバッガであるFirebug等のデバッガツールではファイルの存在を認識できないため、デバッグが非常にやりにくい。この問題はブラウザの進化によりいずれ解決するかもしれないが。

以上の考察から、現状、2、3番目の方法と4番目の方法を混在させた実装が望ましいと考える。

なお、TeaOSでは、読み込み順序に依存しないタスク定義ファイルを「タスク・ファイル」、「タスク・ファイル」よりも前に読み込み完了しなければならないファイルを「ダイナミック・ライブラリ」と定義し、「ダイナミック・ライブラリ」と「タスク・ファイル」の組み合わせの定義を管理することでコンポーネント管理を行っている。 
同一の「ダイナミック・ライブラリ」を複数の「タスク・ファイル」が参照することもあり、また、1つの「タスク・ファイル」が複数回実行されることもあり、各ファイル毎にステートマシンを実装して、多重読み込み回避や同期を実現している。

カテゴリー: html5_multitask | コメントをどうぞ

タスクのイベントハンドラ

 HTMLは元々イベントドリブン方式であり、各タグに対応したハンドラをタスクのイベントハンドラとすることもできる。例えば、以下のような実装である。(※Event.observe()はprototype.jsのAPIである)

(function(){
var task=Sys.getContext();
Event.observe(Sys.$('button1'),'click',function(ev){
	var l=task.lock();
	// anything to do 
	task.unlock(l);
});
})();

 小規模なアプリケーションであれば、このような実装でもいいが、例えば、タスクに優先度をつけたいとか、イベントに遅延をつけたいとか、ユーザ定義イベントを投げたいなどと思ったときに、この方式だけでは都合が悪くなる。論理タスクの優先度とはイベントの優先度であり、それを実装する最も簡単な方法は複数のイベントキューを持つことである。

イベントキューの実装は次のようなものとなる。

Queue=function(){this.q=new Array();}
Queue.prototype={
	push:function(e){this.q.push(e);},
	pop:function(){if(this.q.length>0){return this.q.shift();}return null;},
	size:function(){return this.q.length;},
	empty:function(){return this.q.length==0;}
};

このキューを用いて、
1) initイベント: oninitが呼ばれるまで実行されないイベント。oninitの後は遅延しない。
2) interイベント: 優先度の高いイベント。
3) timerイベント: 一定時間後に発火するタイマー。
といったものを実装した例が下記例である。

Sys={
	q_inter:new Queue,
	q:new Queue,
	q_init:new Queue,
	init_:function(){
		this.dispatchEntry_();
		this.q=this.q_init;
		this.dispatch_();
	},
	send:function(ev,delay){
		if(delay){
			switch(delay){
			case "inter":this.q_inter.push(ev);break;
			case "init":this.q_init.push(ev);break;
			default:setTimeout(this.notifier(ev),delay);break;}}
		else{this.q.push(ev);}
	},
	notify:function(ev,dom){
		this.dispatchEntry_();
		if(dom){ev.dom=dom;}
		this.handler_(ev);
		this.dispatch_();
	},
	dispatch_:function(){
		var q=this.q;
		var ev;
		ev=q_inter.pop();
		if(ev==null){ev=q.pop();}
		while(ev!=null){
			this.handler_(ev);
			ev=q_inter.pop();
			if(ev==null){ev=q.pop();}
		}
	},
	handler_:function(ev){
		var task=this.tasks[ev.tid];
		if(task){
			var l=task.lock();
			task.handler(ev);
			task.unlock(l);
		}
	},
	observe:function(name, hookname, callback){
		var task=Sys.getContext();
		var tag=Sys.$(name);
		Event.observe(tag,hookname,function(ev){
			var l=task.lock();
			(callback)(ev,tag);
			task.unlock(l);
		});
	}
}

これらAPIの利用方法は下記のようになる。

Sys.observe('button1', 'click', (function(ev,dom){
	Sys.notify(ev,dom);
})(ev,dom));
Sys.send({'type':'test1'});
Sys.send({'type':'test2'},'inter');
Sys.send({'type':'test3'},'init');
Sys.send({'type':'test4'},200);

task1.handler=function(ev){
	switch(ev){
	case 'click':
		switch(ev.dom.id){
		case 'button1':break;
		default:break;}
	case 'test1':break;
	case 'test2':break;
	case 'test3':break;
	case 'test4':break;
	default:break;}
}
カテゴリー: html5_multitask | タグ: , , | コメントをどうぞ

タスク間の継承関係

 Linuxのような大規模システムでは、タスクには親子関係がある。(※この場合のタスクとはプロセスのことである。)例えば、X windowのタスクと、X windowの内部で動作するアプリケーションタスクには親子関係があり、例えば、親タスクが終了すれば子タスクも終了したり、親タスクから子タスクへのアクセスが出来たり、親タスクが子タスクのウインドウ枠を作成したりと、ある程度の親子タスク間で役割分担をしている。

 Webシステムでも「タスク」の親子関係があると便利である。例えば、Webページ全体のデザインを管理するタスク、Widgetを管理するタスクとの間には親子関係があったほうが柔軟な実装が可能になる。タスク間の親子関係の実装例が下記である。


Task.prototype={
initialize:function(){
var p=Sys.getContext();
this.parent=p;
if(p){p.children.push(this);}
this.children=new Array;
this.resources=new Object;

},
find:function(rscName){
if(this.resources[rscName]){return this.resources[rscName];}
var task=this.parent;
while(task!=null){
if(task.resources[rscName]){return task.resources[rscName];}
task=task.parent;
}
return null;
},
handler:function(ev){
switch(ev.type){
case “t.destroy”:this.destroy__();break;

}
},
destroy__:function(){
var o=this.instance;
var p=this.parent;
this.parent=null;
if(p){
var ch=p.children;
for(var i=0;i

カテゴリー: html5_multitask | タグ: , , | コメントをどうぞ

ローカルIDとタスクコンテキスト

 さて、マルチタスク化の際にまず問題になるのは、DOM要素のIDやタスク内変数の独立性である。本記事ではそれらについて解説していく。

 HTMLではタグにアクセスしてDOM操作をするという処理が多いが、下記例のようにタグを特定する際にIDを介する。

<div id="tag1"></div>
var applicationData="This's data.";
window.onload=function(){
	var tag=document.getElementById("tag1");
	tag.style.display='none';
	tag.innerHTML=applicationData;
}

 IDとはページに対してユニークであるため、一つのページに複数のタスク(この場合、物理タスクでも論理タスクでもよい。独立して動作するアプリケーション。)があった場合に、それぞれのタスクに定義したIDが競合する可能性がある。また、アプリーケーション内でグローバル変数を使うとタスク同士でグローバル変数が競合することもある。そこでIDにプレフィックスを付けて競合を防ぎたいが、その際に付けるプレフィックスはアプリケーション名ではなく、タスクID(タスクを起動する際に発行される数字。)とする。同じアプリケーションを複数起動する可能性もあり、その際に名前がぶつかっては困るからである。

 以下に実装例を示す。

Sys={
	tid_:0, //last task id
	task:null, //current task object
	tasks:{}, // all task list
	gid:function(name){return (this.task.id+"_"+name);},
	lid:function(name){var i=name.indexOf("_");return (i!=-1)?name.substr(i+1):name;},
	cid2tid:function(name){var i=name.indexOf("_");return (i!=-1)?name.substr(0,i):0;},
	$:function(name){return document.getElementById(this.task.id+"_"+name);},
	getContext:function(){return this.task;},
	tid:function(){return this.task.id;},
	selectTask_:function(task){this.task=task;}
}
Task=function(){
	this.id=++Sys.tid_;
	Sys.tasks[this.id]=this;
}
Task.prototype={
	lock:function(){
		var task=Sys.getContext();
		Sys.selectTask_(this);
		return task;
	},
	unlock:function(task){NSys.selectTask_(task);}
}

 大雑把に使い方を説明すると、Sys.gid(name)でタスク内コントロールをHTMLのIDに変換し、Sys.$(name)でDOM elementを取得する。Sys.getContext()で自タスクのコンテキストを取得し、Sys.tid()で現在のタスクIDを取得する。上記クラスを用いた書き方は下記のようになる。

var task1=new Task();

(function(){
	var l=task1.lock();
	// the beginning of task.oncreate
	Sys.getContext().applicationData="This's data.";
	document.write('<div id="'+Sys.gid('tag1')+'"></div>');
	// the end of task.oncreate
	task1.unlock(l);
})();

window.onload=function(){
	var l=task1.lock();
	// the beginning of task.oninit
	var tag=Sys.$("tag1");
	tag.style.display='none';
	tag.innerHTML=Sys.getContext().applicationData;
	// the end of task.oninit
	task1.unlock(l);
}

 なお、lock()/unlock()は一見排他処理に見えるかもしれないが、実際はタスクのカレントコンテキストを切り替えているだけである。lock()の返り値はlock前のタスクオブジェクトで、これをunlock()時に引数渡しすることで、リカーシブ・ロックのような使い方も可能になる。Win32APIのSelectObject()のような使い方だと言えばわかりやすい(人もいる)だろう。

 「マルチタスク」対応のためにlock()/unlock()をいちいち呼び出すのは面倒くさそうに見えるが、より実用的な実装ではイベントハンドラにてこれらの呼び出しを隠蔽する。参考のためにTeaOS的な書き方をすると下記のようになる。

(function(){
var App=Class.create({
	initialize:function(args){
		this.data="This's data.";
		Sys.find('frame').write('<div id="'+Sys.gid('tag1')+'"></div>');
	},
	handler:function(ev){
		switch(ev.type){
		case 't.init':{
			var tag=Sys.$("tag1");
			tag.style.display='none';
			tag.innerHTML=this.data;
			break;}
		default:break;}
	}
});
Sys.registerExecutable({
	create:function(args){return new App(args);}
});
})();

さて、余談であるが、この例ではHTML文書を動的に生成している。document.createElement()などのDOM操作を用いて、文書を生成したほうがコードの見栄えが良く、また、型チェックの問題が生じなくて良いという意見もあるかと思う。しかし、私は以下の理由から、出来るだけHTML文書を文字列で生成する方式を使うようにしている。(もちろん、DOM操作のほうが便利な場合はDOM操作を使うが。)

  • 文字列のHTML文書を渡した場合、構文解析するのはブラウザ内のネイティブコードであり高速。一方、Javascriptで細かくDOM操作の手続きを記述するとインタープリタによる処理となるためオーバーヘッドが多くなり遅い。
  • クライアントサイドで処理をしたくない場合に、サーバサイドJavascriptに処理を委譲するという選択肢もあるが、サーバサイドで複雑なDOMオブジェクトを構築すれば表示する際に多くの文字列への変換手続きを経なければならず遅くなるし、実装も困難になる。すなわち、文字列を生成する方法のほうがサーバサイドとクライアントサイドの実装がシームレスに可換である。
  • DOMに対する操作は親のDOMが生成された後のタイミングで可能になる。そのため、onload後に何度もDOM操作をすることになり、ブラウザの画面がフラッシュが何度も発生し表示が遅くなる可能性がある。(ただし、ブラウザの実装による。)
カテゴリー: html5_multitask | タグ: , , , | コメントをどうぞ

HTML5のマルチタスク化とは

 アーキテクト用語でマルチタスクといった場合、タイムスライス等によりワースト応答時間が保証される物理タスクと、コンポーネントの独立性を表す単位としての論理タスクといった2つの意味がある。従来のHTMLでは、Javascriptはシングル(物理)タスクであったため、また、JavascriptはWebサービスの機能を補完する位置づけであり論理タスク分割の必要性が低かったため、マルチタスク化という発想はあまりなかった。

 HTML5になり、この様相が変わってくると考えている。Application Cacheという技術により、コンポーネント管理をして複雑なアプリケーションを組み上げていったり、Webと独立して複数のアプリケーションを同時に動かして管理するようなことが出来るようになった。また、Web Workersという技術により物理タスクと同等の機能を実現できるようになった。さらに言えば、HTML5とは直接関係はないが、サーバサイドJavascriptの技術も出てきたことで、より柔軟にサーバサイドとクライアントサイドの処理の切り分けが出来るようになった。

 すなわち、これら新規格により今までに無かったような本格的なマルチタスク環境が構築できるようになったと言えよう。しかし、標準的な技術だけではコンポーネント化やマルチタスク化などに必要な基本的な機能が足りず、各開発者がそれを補完するために独自の実装をすれば、タスクをコンポーネントとして使いまわすことが難しくなる。また、ブラウザベースでタスク単位で動作するユニットテストや構成管理等の開発環境と実行環境を兼ね備えた統合環境が欲しくなるが、現状、そのようなものは見当たらない。

 そこで、このシリーズではHTML5における適切なマルチタスク化とは何か?について解説していく。

 なお、一般に、論理タスクとは、自身にバインドされたコンテキストやコントロールといったリソースをもち、イベントハンドラにより駆動するという特徴を持つものと定義される。Javascriptも、イベント駆動であり、また、DOMを介してコントロールを制御することから、この定義はそのままJavascriptに対しても当てはまるため、この定義をそのまま利用する。

カテゴリー: html5_multitask | タグ: , , , | コメントをどうぞ

ローカルとサーバのデータを同期する

 WebStorageを始めとするHTML5関連技術により生じた最大の変化は、オフライン(クライアントサイド)処理の強化である。これは言い換えれば、「Webサービスを実現するための補完言語としてのJavascript」という位置付けから、「Javascriptで書かれたローカルアプリケーションとシームレスに繋がるWebサービス」という位置づけへの転換である。この概念の変化は、いくつかの大きな問題を生む。その1つは、オフラインで動作しているローカルアプリケーションのデータと、サーバサイドのデータベースの同期の問題である。

 同期の問題に関連して、特に多人数で一つのデータにアクセスするようなシステムにおいてユーザアクションのコンフリクトという悩ましい問題を生み出す。もちろん、従来型のシステムでも同様にコンフリクトの問題はあったが、従来型のシステムでは、ユーザがサーバからデータを取得して編集し再びサーバに保存するまでの時間(以下、「アクセス時間」と呼ぶ)は短時間であった。それに対し、ローカルで独立したシステムの場合は「アクセス時間」が非常に長くなり、コンフリクトする可能性が高くなる。従来型のシステムでは、コンフリクトを防ぐためにファイルを排他ロックし、1人の編集者が他の編集者の書き込みアクセスをブロックやり方がしばし用いられてきたが、「アクセス時間」が長いシステムにおいては排他ロックするやり方は他のユーザに制限を与え不便な場合が多い。

 このような「アクセス時間」の長いシステムにおいてサーバ側とローカル側のデータの同期を解決するシステムとして典型的なものに構成管理ツールがある。構成管理ツールでは、ローカルの編集差分やサーバの更新差分をやり取りして、編集箇所のコンフリクトがあれば、適宜対応するやり方をとるが、ローカルで独立して動作する大規模アプリケーションを実装する際には、このようなアプローチが必須になると考える。

 TeaOSで取っているアプローチは、全ての書き込み可能なディレクトリ、ファイル、もしくはクラスターに対して、ダーティーフラグを立てるやり方である。下記例にあるように、属性’d’に対して、1が新規作成フラグ、2が更新フラグ、3が削除フラグである。

var createdFile={'d':1,'t':'lf','C':"This's data."};
var modifiedFile={'d':2,'t':'lf','C':"This's data."};
var removedFile={'d':3,'t':'lf','C':"This's data."};

 実際には、ユーザはダーティフラグを意識せずとも下記のようなAPIを実行することでフラグが設定される。

var file=new File('/home/teaos/1.txt','w');
...
file.close();

ちなみに、ローカルの変更をサーバに反映させるにはFSys.commit(path,options), サーバの変更をローカルに反映させるにはFSys.update(path,options)を呼び出す。更新先のサービスによって処理が異なってくるが、その差異はドライバ層で吸収する構造になっている。

なお、TeaOSでは、ファイルやディレクトリにメタ情報を付加することができるが、このメタ情報についても差分管理をしている。差分情報を細かく管理すればより柔軟な設計が可能になる。

カテゴリー: html5_storage | タグ: , , , | コメントをどうぞ

WebStorageとWebデータを区別無くアクセスする

(※この記事は書きかけです。)

 WebStorageには、LocalStorageやSessionStorageがあり、また、類似のものとして「CacheStorage」(*1)もある。これらの間に機能的な違いはなく、単にそのデータのライフサイクルが異なるだけである。このような考えを広げていけば、ローカルのデータのみならず、インターネット上にある全てのデータは、同じような形で抽象化することができよう。また、WebSocketのような非同期通信をするような機能についても同様である。ただし、データの抽象化のレベルはWebStorageと同じレベルとするより、ファイルシステムと同等のレベルまで上げたほうが汎用性があるため、ここではファイルシステムと同等のレベルの抽象化について書いていく。

例えば、前回の記事「WebStorageを用いてツリー構造を持つデータを実装する」にて説明したクラスター定義のスキーマで、’t’属性に’sC'(server cluster)を指定できるようにすれば、下記例のように、サーバ上のJSONファイルを直接クラスターとして指定することもできる。同様に、ファイル定義を拡張して’t’属性に’sl'(server link)を指定できるようにすれば、サーバ上のファイルとファイルシステム上のファイルを関連付けすることもできる。

var cluster={'t':'sC', 'cluster':'http://www.nayuta-works.com/userdata/index.cluster'};
var file={'t':'sl','C':'http://www.nayuta-works.com/userdata/memo.txt'};

 これを応用すれば、様々なCMSエンジンから自システムのスキーマに沿った形式のドキュメントを生成させることで、簡単にCMSエンジンと連携させることができる。しかし、自分でカスタマイズできないサービスも多数ある。この場合、解釈しなければならないデータのフォーマットは多様であり、自システムで解釈できる形に変換する必要がある。変換する際に同じスキーマで解釈できるようにすれば、一つのアプリケーションで様々なサイトのデータを簡単に取り扱えるようになる。

そこで、ドライバのようなコンポーネントでサイトやファイル形式やサービスごとの差異を吸収し、アプリケーションはドライバにより正規化されたデータを扱うというアプローチを考える。

参考のためTeaOSで実現しているやり方を示すと、mountコマンドを用いて、ファイルシステムのパスにドライバやターゲットのURLを関連付けることで、アプリケーションは正規化されたデータ形式にアクセスできるようになるというものである。実装例としては下記のようなものとなる。

FSys.mount('/share/sites/nayuta-educations.com/blog',{'driver':'standard-wordpress','url':'www.nayuta-educations.com/blog','controller':'filter','force':true});
var list=FSys.ls('/share/sites/nayuta-educations.com/blog');
FSys.unmount('/share/sites/nayuta-educations.com');
カテゴリー: html5_storage | タグ: , , , | コメントをどうぞ

WebStorageを用いてツリー構造を持つデータを実装する

 WebStorageでは、Hash型のデータ構造しかサポートしていないが、多くのアプリケーションのデータ構造というのはツリー構造であり、ここではツリー構造の実装の仕方を解説する。

 なお、WebStorageの関連技術として、ディレクトリ-ファイル形式をサポートするものもあるが、ブラウザにより対応状況が大きく異なる上に、標準規格から外れるため、本ブログでは対象としない。

 簡単な実装方法として、要素名に「親要素名/子要素名/孫要素名」というようにパス構造をつける方法が挙げられる。以下にその例を示す。ただし、変数storageはStorageクラスの派生クラスとする。

storage.set('/home/test/case1.txt','data1');
storage.set('/home/test/case2.txt','data2');
storage.set('/home/test/case3.txt','data3');

この方法だと、ある要素の子要素を見つける際に、localStorage内の全要素を検索しないといけなくなるため、要素数が増大していくと要素探索時間がどんどん長くなる。つまり、スケーラビリティが失われるという問題があるため、大規模なアプリケーションを構築する際には推奨できない。

 より現実的なアプローチでツリー構造を実現する方法で簡単なのは変数をJSON形式に変換してLocalStorageに保存する方法である。下記にその例を示す。

var cluster={
	'D':{ // 'D' means sub directories
		'test':{
			'F':{ // 'F' means sub files
				"case1.txt":"data1",
				"case2.txt":"data2",
				"case3.txt":"data3"
			}
		}
	}
};
storage.set('home',Object.toJSON(cluster));

 この方法にも弱点がある。例えば、データ量の多いツリーをJSON形式にエンコードした場合、データサイズが大きくなりデコードに時間がかかる。巨大なアプリケーションでは、保存したデータの一部分のみを使うというケースの方が多いため、関連性の高いデータ群をクラスター化して、各クラスターをJSON形式に変換するアプローチのほうがスケーラビリティに優れたアプローチだといえよう。

 ここでは、クラスター定義のスキーマの例として、TeaOSで採用しているやり方を説明する。まず、下記に実装例を示す。

var cluster2=Object.toJSON({'D':{},'F':{}});
var root={ // root cluster
	'D':{ // sub directories
		'D1':{'t':'lD', 'D':{}, 'F':{}},
		'D2':{'t':'lC', 'cluster':storage.allocate('lc://', cluster2)}
	},
	'F':{ // sub files
		'1.txt':{'t':'if', 'C':'file context1'},
		'2.txt':{'t':'if', 'C':'file context2'}
	}
};
storage.set('@ftree',Object.toJSON(root));

 ルートクラスターは要素名’@ftree’に保存する。なお、クラスターには、ディレクトリ定義のハッシュテーブル’D’とファイル定義のハッシュテーブル’F’が定義されている。各ディレクトリの’t’属性にはクラスター型かディレクトリ型かを特定する型情報をを指定する。’t’属性が’lD’の場合は、ディレクトリ型であり、親ディレクトリと同じクラスターに保存される。’t’属性が’lC’の場合は、クラスター型であり、親ディレクトリと異なるクラスターに保存される。その場合、保存されている場所(要素名)は’cluster’属性に設定されている。簡単に言えば、’t’属性に’lC’を指定することで、自由に別クラスターを定義することができ、関連性の低いデータを別クラスターにすることを可能にしている。ちなみに、’lD’とはlocal directoryの略で、’lC’とはlocal clusterの略であるが、例えば、’sC'(server cluster)というように新たな’t’属性を定義することで、より柔軟なデータ構造が実現できる。このことについては次の記事で説明しよう。

 なお、TeaOSでは、このようなデータアクセスを容易にするための手段として様々なファイルアクセスAPIを用意しているが、下記はその例である。

FSys.mkdir('/D1');
FSys.mkdir('/D2',{'cluster':true});
FSys.touch(['/1.txt','/2.txt']);

なお、大規模なアプリケーションを構築する際には、今回解説した保存の他に、デコードしたデータをキャッシュしたり、より複雑なデータ構造をサポートしたり、暗号化したりなど様々な機能をサポートすることも必要になるが、TeaOSのファイルシステムはそのような処理を全てユーザから隠蔽している。

カテゴリー: html5_storage | タグ: , , , , | コメントをどうぞ

WebStorageを用いて名前無しデータを実装する

 WebStorageはHashタイプのデータなので、要素にいちいち名前をつけないといけない。これだと要素に多重度がある場合にわずらわしい。そこで、名前無しデータを動的に取得する方式を考える。実装方式は色々とあるが、今回は乱数を発生させて、それをプレフィックスとして設定する例を示す。なお、ここでStorageクラスはLocalStorageクラスやSessionStorageクラスの親クラスだとする。

Storage.prototype={
	initialize:function(){this.maxCount=10000000;},
	allocate:function(type,itemValue){
		var mC=this.maxCount;
		var id=type+parseInt(Math.random()*mC);
		while(this.get(id)!=null){id=type+parseInt(Math.random()*mC);}
		this.set(id, itemValue);
		return id;},
	deallocate:function(id){this.unset(id);}
}

(メンバ関数のget(), set(), unset()は子クラスに実装するが、詳しくは「WebStorageのドメイン内競合を防止する」を参照のこと。)

この実装例だと、maxCountの値が全要素の数に対して十分に大きくないとループ回数が多くなり遅くなる。最悪、無限ループに陥ることもありうるが、LocalStorageの領域はサイズがそれほど大きくなく、ここに設定する要素数のオーダーが数百万という単位にならないという前提でこういった実装にしている。目的に応じて適宜実装を変えるのも手である。以下にこのクラスの利用例を示す。

var id1=LocalStorage.allocate('dataA',"This's first.");
var id2=LocalStorage.allocate('dataA',"This's second.");
...
LocalStorage.deallocate(id1);
LocalStorage.deallocate(id2);
カテゴリー: html5_storage | タグ: , , , , | コメントをどうぞ

WebStorageのエクスポート・インポート機能を実装する

 一般にLocalStorageやSessionStorageにはWebアプリケーションの作業データなどユーザが加工したデータが入っているため、ユーザがブラウザ管理領域以外の場所にバックアップをとっておきたいとか、違うブラウザにデータを移動させたいといったニーズがあるかと思う。そこで、WebStorageデータのエクスポート・インポート機能の実装例を示そう。

まず、インポートデータ、エクスポートデータのフォーマットを決める。下記の例はTeaOSで用いているフォーマットである。

提案するフォーマットは、行単位で、コマンドもしくは文章を指定するものである。行頭に@が付いていた場合はコマンド、ついていない場合は文章である。ただし、@@というように行頭の@マークが2つ連続している場合は、先頭文字が@である文章とみなされる。

まず、一般的なデータ例を示す。@bコマンドと@eコマンドで囲まれた領域が、WebStorageの要素の値であり、@bの行に定義された文字列が要素名である。

@b itemName1
itemValue1
@e
@b itemName2
itemValue2
@e

このデータ形式は、要素の中にエクスポートデータ形式が入っていた場合に、データ構造が壊れないよう必要な部分をエスケープする仕様になっている。下記はその例である。

@b itemName3
@@b itemName3
itemValue3
@@e
@e

メンバ関数は下記のように定義する。実装は簡単だが少々長いので割愛する。

Storage.prototype={
	import_:function(str){...},
	export_:function(){... return str;}
}
カテゴリー: html5_storage | タグ: , , , , | コメントをどうぞ