Commit 380a62c2 authored by Tristan Cavelier's avatar Tristan Cavelier

First Commit!

parents
Javascript Input/Output
=======================
+ [What is jIO?](#what-is-jio)
+ [How does it work?](#how-does-it-work)
+ [Getting started](#getting-started)
+ [Where can I store documents?](#where-can-i-store-documents)
[**For developers**](#for-developers)
+ [Quick start](#quick-start)
+ [How to design your own jIO Storage Library](#how-to-design-your-own-jio-storage-library)
+ [Error status](#error-status)
+ [Job rules](#job-rules)
+ [Code sample](#code-sample)
[**Authors**](#authors)
[**Copyright and license**](#copyright-and-license)
What is jIO?
------------
jIO is a JavaScript Library that stores/manipulates documents on storage servers over the internet in an asynchronous fashion.
How does it work?
-----------------
jIO is separated in 2 parts, core library and storage library(ies). The core must use some javascript objects (storages) to interact with the associated remote storage servers. jIO uses job management, so every request adds jobs in a queue which is saved on browser local storage in order to be restored later if the browser crashed or else. jIO will invoke all these jobs at the same time. Of course, adding an already ongoing job won't work, so there will be no conflicts.
Getting started
---------------
This short tutorial is designed to help you get started using jIO. First, download the jIO [core](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/jio.js?js=1) ([min](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/jio.min.js?js=1)) and the jIO [storages](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/jio.storage.js?js=1) ([min](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/jio.storage.min.js?js=1)) scripts and their dependencies ([LocalOrCookieStorage](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/localorcookiestorage.js?js=1) ([min](http://git.erp5.org/gitweb/ung.git/blob_plain/refs/heads/master:/OfficeJS/lib/jio/localorcookiestorage.min.js?js=1)), [jQuery](http://jquery.com), [base64](http://www.webtoolkit.info/javascript-base64.html), [sjcl](http://crypto.stanford.edu/sjcl/), [sha2](http://anmar.eu.org/projects/jssha2/)). Then, add the scripts in your HTML page as following:
```
<!-- jIO Core -->
<script src="localorcookiestorage.js"></script>
<script src="jio.js"></script>
<!-- Some storage dependencies -->
<script src="jquery.js"></script>
<script src="base64.js"></script>
<script src="sjcl.js"></script>
<script src="sha2.js"></script>
<!-- Some storage -->
<script src="jio.storage.js"></script>
```
+ jquery.js - see http://jquery.com
+ localorcookiestorage.js - is a small library that stores persistent data on local storage even if the browser does not support HTML 5.
+ base64.js - is a small library to encrypt data into base64.
+ sjcl.js - is a powerful library to encrypt/decrypt data.
+ sha2.js - is a small library to hash data.
+ jio.js - is the jIO core.
+ jio.storage.js - is a jIO Storage library that can interact with some remote storage servers.
The jIO API provides 5 main methods:
+ `post` - Creates a new file in the storage
+ `get` - Reads a file from the storage.
+ `put` - Updates a file in the storage.
+ `remove` - Deletes a file from the storage.
+ `allDocs` - Gets a list of existant files.
```
var my_jio_instance = jIO.newJio(storagedescription);
my_jio_instance.post (doc[, options][, callback|, success, error]);
my_jio_instance.get (docid[, options][, callback|, success, error]);
my_jio_instance.put (doc[, options][, callback|, success, error]);
my_jio_instance.remove (doc[, options][, callback|, success, error]);
my_jio_instance.allDocs ([options][, callback|, success, error]);
my_jio_instance.stop(); // stops momentarily the job manager
my_jio_instance.start(); // restart the job manager
my_jio_instance.close(); // close this instance
```
Examples:
```
var jio = jIO.newJio({"type":"local","username":"myname","applicationname":"myappname"});
jio.get ('myfile',{max_retry:3},function (err,val) {
if (err) {
console.error (err);
} else {
console.log (val.content);
}
});
jio.put ({_id:'myotherfile',content:'and his content'},function (val) {
console.log ('success');
},function (err) {
console.error (err);
});
```
Where can I store documents?
----------------------------
These are the available storage descriptions provided by jio.storage.js:
- LocalStorage, to manipulate files on browser local storage.
`{"type":"local","username":<string>,"applicationname":<string>}`
- DAVStorage, to manipulate files on a webDAV storage server.
`{"type":"dav","username":<string>,"password":<string>,"applicationname":<string>}`
- ReplicateStorage, to manipulate files on several storage.
`{"type":"replicate","storagelist":[<storagedescription>, ...]}`
- IndexStorage, to index sub storage files.
`{"type":"index","storage":<storagedescription>}`
- CryptStorage, to encrypt/decrypt sub storage files.
`{"type":"crypt","username":<string>,"password":<string>,"storage":<storagedescription>}`
- ConflictManagerStorage, to manage sub storage files revision and conflicts.
`{"type":"conflictmanager","storage":<storagedescription>}`
For developers
==============
Quick start
-----------
+ **Clone repository** `git clone http://git.erp5.org/repos/ung.git`. Sources are there: `${repo}/OfficeJS/src/`.
+ **Build** - Go to `${repo}/OfficeJS/grunt/`, and you can execute the script `gruntall` or build everycomponent manually with `make`.
+ **Dependencies** - [Grunt](https://github.com/cowboy/grunt) - [JSHint](https://github.com/jshint/jshint) - [UglifyJS](https://github.com/mishoo/UglifyJS) - [PhantomJS](http://phantomjs.org)
+ **Tests** - Go to `${repo}/OfficeJS/tests/`, and you can open `jiotests_withoutrequirejs.html` from localhost. (Tests with requireJS are not available yet.)
How to design your own jIO Storage Library
-----------------------------------------
jIO basicStorage interface must be inherited by all the new storages. Seven methods must be redesigned: `post, get, put, remove, allDocs, serialized, validateState`
Except 'serialized' and 'validateState', the above methods must end with 'success','retry' or 'error' inherited methods, which can have only one parameter.
This parameter is the job return value, it is very important.
The return value must seams like PouchDB return values.
```
var val = {
ok: <often true>, // true
id: <the file path> // 'file.js'
// You can add your own return values
};
success(val);
var err = {
status: <often http error status>, // 404
statusText: <often http error statusText>, // 'Not Found'
error: <the error name>, // 'not_found'
reason: <short description>, // 'file not found'
message: <description> // 'Cannot retreive file.....'
// You can add your own error values
};
error or retry(err);
```
The method 'serialized' is used by jIO to store a serialized version of the storage inside the localStorage in order to be restored by jIO later.
```
var super_serialized = that.serialized;
serialized = function () {
var o = super_serialized();
o.important_info1 = '...';
o.important_info2 = '...';
return o;
};
```
When jIO try to restore this storage, 'important_info1' and 2 will be given in the storage spec.
**CAUTION**: Don't store confidential informations like passwords!
The method 'validateState' is used by jIO to validate the storage state. For example, if the storage specifications are not correct, this method must return a non-empty string. If all is ok, it must return an empty string.
```
validate = function () {
if (spec.important_info1) {
return '';
}
return 'Where is my important information ??';
};
```
The storage created, you must add the storage type to jIO.
jIO.addStorageType() require two parameters: the storage type (string) add a constructor (function). `jIO.addStorageType('mystoragetype', myConstructor);`
To see what this can look like, see [Code sample](#code-sample).
Error status
------------
* 0: Unknown Error
* >9 & <20: Job Errors
* 10: Stopped, The Job has been stopped by adding another one
* 11: Not Accepted, The added Job cannot be accepted
* 12: Replaced, The Job has been replaced by another one
* >19 & <30: Command Errors
* 20: Id Required, The Command needs a document id
* 21: Content Required, The Command needs a document content
* >29: Storage Errors
* >99: HTTP Errors
Job rules
---------
jIO job manager will follow several rules set at the creation of a new jIO instance. When you try to do a command, jIO will create a job and will make sure the job is really necessary. Thanks to job rules, jIO knows what to do with the new job before adding it to the queue.
You can add your own rules like this:
```
var jio = jIO.newJio(<storagedescription>);
var jio_rules = jio.getJobRules();
// When a 'put' job is on going (true), and we add a 'get' job,
// then the 'get' job must wait for the end of the 'put' job.
jio_rules.addActionRule('put', true /* on going */, 'get', jio_rules.wait);
```
+ `wait` - wait until the end of the current job
+ `update` - replace the current job by this one
+ `eliminate` - eliminate the current job, and add the new one
+ `dontAccept` - the new job cannot be accepted
+ `none` - simply add the new job to the job queue
You can make special rules like this:
```
var putput = function(job1,job2){
if (job1.getCommand().getDocInfo('content') ===
job2.getCommand().getDocInfo('content')) {
return jio_rules.dontAccept();
} else {
return jio_rules.wait();
}
};
jio_rules.addActionRule('put', true, 'put', putput);
```
**Default rules**:
```
var putput = function(job1,job2){
if (job1.getCommand().getDocInfo('content') ===
job2.getCommand().getDocInfo('content')) {
return jio_rules.dontAccept();
} else {
return jio_rules.wait();
}
};
jio_rules.addActionRule('post',true ,'post', putput);
jio_rules.addActionRule('post',true ,'put', putput);
jio_rules.addActionRule('post',true ,'get', jio_rules.wait);
jio_rules.addActionRule('post',true ,'remove',jio_rules.wait);
jio_rules.addActionRule('post',false,'post', jio_rules.update);
jio_rules.addActionRule('post',false,'put', jio_rules.update);
jio_rules.addActionRule('post',false,'get', jio_rules.wait);
jio_rules.addActionRule('post',false,'remove',jio_rules.eliminate);
jio_rules.addActionRule('put',true ,'post', putput);
jio_rules.addActionRule('put',true ,'put', putput);
jio_rules.addActionRule('put',true ,'get', jio_rules.wait);
jio_rules.addActionRule('put',true ,'remove',jio_rules.wait);
jio_rules.addActionRule('put',false,'post', jio_rules.update);
jio_rules.addActionRule('put',false,'put', jio_rules.update);
jio_rules.addActionRule('put',false,'get', jio_rules.wait);
jio_rules.addActionRule('put',false,'remove',jio_rules.eliminate);
jio_rules.addActionRule('get',true ,'post', jio_rules.wait);
jio_rules.addActionRule('get',true ,'put', jio_rules.wait);
jio_rules.addActionRule('get',true ,'get', jio_rules.dontAccept);
jio_rules.addActionRule('get',true ,'remove',jio_rules.wait);
jio_rules.addActionRule('get',false,'post', jio_rules.wait);
jio_rules.addActionRule('get',false,'put', jio_rules.wait);
jio_rules.addActionRule('get',false,'get', jio_rules.update);
jio_rules.addActionRule('get',false,'remove',jio_rules.wait);
jio_rules.addActionRule('remove',true ,'get', jio_rules.dontAccept);
jio_rules.addActionRule('remove',true ,'remove',jio_rules.dontAccept);
jio_rules.addActionRule('remove',false,'post', jio_rules.eliminate);
jio_rules.addActionRule('remove',false,'put', jio_rules.eliminate);
jio_rules.addActionRule('remove',false,'get', jio_rules.dontAccept);
jio_rules.addActionRule('remove',false,'remove',jio_rules.update);
jio_rules.addActionRule('allDocs',true ,'allDocs',jio_rules.dontAccept);
jio_rules.addActionRule('allDocs',false,'allDocs',jio_rules.update);
```
Code sample
-----------
Storage example:
```
(function () {
var newLittleStorage = function ( spec, my ) {
var that = my.basicStorage ( spec, my );
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.firstname = spec.firstname;
o.lastname = spec.lastname;
return o;
};
that.validateState = function () {
if (spec.firstname && spec.lastname) {
return '';
}
return 'This storage needs your firstname and your lastname.';
};
that.post = function (command) {
// [code]
that.success({ok:true,id:command.getDocId()});
};
that.put = function (command) {
// [code]
that.success({ok:true,id:command.getDocId()});
};
that.get = function (command) {
// example with jQuery
jQuery.ajax ( {
url: 'www.google.com',
type: "GET",
async: true,
success: function (content) {
that.success({
_id:command.getDocId(),content:content,
_last_modified:123,_creation_date:12
});
},
error: function (type) {
type.reason = 'error occured';
type.message = type.reason + '.';
type.error = type.statusText;
that.error(type);
}
} );
};
that.allDocs = function (command) {
// [code]
if (!can_I_reach_the_internet) {
// Oh, I can't reach the internet...
that.retry({
status:0,error:'unknown_error',
statusText:'Unknown Error',
reason:'network_not_reachable',
message:'Impossible to reach the internet.'
});
} else {
// Oh, I can't retreive any files..
that.error({
status:403,error:'forbidden',
statusText:'Forbidden',
reason:'unable to get all docs',
message:'This storage is not able to retreive anything.'
});
}
};
that.remove = function (command) {
// [code]
that.success({ok:true,id:command.getDocId()});
};
return that;
};
jIO.addStorageType ('little', newLittleStorage);
}());
```
Authors
=======
+ Francois Billioud
+ Tristan Cavelier
Copyright and license
=====================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>jIO Complex Example</title>
</head>
<body>
<script type="text/javascript">
<!--
var logcolor = 'cyan';
var logGetColor = function () {
if (logcolor === 'white') {
logcolor = 'cyan';
} else {
logcolor = 'white';
}
return logcolor;
};
var log = function (o) {
var node = document.createElement ('div');
node.setAttribute('style','background-color:'+logGetColor()+';');
if (typeof o === 'string') {
node.textContent = o;
} else {
node.textContent = JSON.stringify (o);
}
document.getElementById('log').appendChild(node);
};
var error = function (o) {
var node = document.createElement ('div');
node.setAttribute('style','background-color:'+logGetColor()+
';color:red;font-weight:bold');
if (typeof o === 'string') {
node.textContent = o;
} else {
node.textContent = JSON.stringify (o);
}
document.getElementById('log').appendChild(node);
};
var clearlog = function () {
document.getElementById('log').innerHTML = '';
};
//-->
</script>
<div id="main">
<table>
<tr style="font-style:italic;">
<th>simple storage</th><th>multi storage</th><th>distant storage</th>
<th>conflict managing</th><th>custom storage description</th>
</tr>
<tr>
<th>local</th><th>crypt & local</th><th>dav</th>
<th>conflictmanager & local</th><th>custom</th>
</tr>
<tr>
<th>
<input type="text" id="localuser" value="localuser" placeholder="username" /><br />
<input type="text" id="localapp" value="localapp" placeholder="applicationname" /><br />
</th>
<th>
<input type="text" id="cryptuser" value="cryptuser" placeholder="username" /><br />
<input type="text" id="cryptapp" value="cryptapp" placeholder="applicationname" /><br />
<input type="password" id="cryptpassword" value="pwd" placeholder="password" /><br />
</th>
<th>
<input type="text" id="davuser" value="" placeholder="username" /><br />
<input type="text" id="davapp" value="" placeholder="applicationname" /><br />
<input type="password" id="davpassword" value="" placeholder="password" /><br />
<input type="text" id="davurl" value="" placeholder="url" /><br />
</th>
<th>
<input type="text" id="conflictuser" value="localuser" placeholder="username" /><br />
<input type="text" id="conflictapp" value="localapp" placeholder="applicationname" /><br />
</th>
<th style="width:100%;">
<textarea id="customstorage" style="width:100%;">{&quot;type&quot;:&quot;local&quot;,&quot;username&quot;:&quot;customuser&quot;,&quot;applicationname&quot;:&quot;customapp&quot;,&quot;customkey&quot;:&quot;customvalue&quot;}</textarea>
</th>
</tr>
<tr>
<th><button onclick="newLocalJio()">Create New jIO</button></th>
<th><button onclick="newCryptJio()">Create New jIO</button></th>
<th><button onclick="newDavJio()">Create New jIO</button></th>
<th><button onclick="newConflictJio()">Create New jIO</button></th>
<th><button onclick="newCustomJio()">Create New jIO</button></th>
</tr>
</table>
</div>
<hr />
<input type="text" id="filepath" value="" placeholder="filepath" />
<input type="text" id="content" value="" placeholder="content" />
<input type="text" id="prev_rev" value="" placeholder="previous revision" />
<br />
<label for="show_conflicts">Show Conflicts</label>
<input type="checkbox" id="show_conflicts" />,
<label for="show_revision_history">Show Revision History</label>
<input type="checkbox" id="show_revision_history" />,
<label for="show_revision_info">Show Revision Info</label>
<input type="checkbox" id="show_revision_info" />,
<label for="last_revision">Remove Last Revision</label>
<input type="checkbox" id="last_revision" />,<br />
<label for="max_retry">Max Retry</label>
<input type="number" id="max_retry" value="0" style="width:30px;"/>
(0 = infinite),
<label for="metadata_only">Metadata Only</label>
<input type="checkbox" id="metadata_only" />
<hr />
<button onclick="post()">post</button>
<button onclick="put()">put</button>
<button onclick="get()">get</button>
<button onclick="remove()">remove</button>
<button onclick="allDocs()">allDocs</button><br />
<button onclick="printLocalStorage()">print localStorage</button>
<button onclick="localStorage.clear()">clear localStorage</button><br />
<button onclick="clearlog()">Clear Log</button>
<hr />
<div id="log">
</div>
<script type="text/javascript" src="../localorcookiestorage.js"></script>
<script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../lib/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../lib/base64/base64.js"></script>
<script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script>
<script type="text/javascript" src="../lib/jsSha2/sha2.js"></script>
<script type="text/javascript" src="../jio.storage.js"></script>
<script type="text/javascript">
<!--
var my_jio = null;
var newLocalJio = function () {
var localuser, localapp;
localuser = $('#localuser').attr('value');
localapp = $('#localapp').attr('value');
var spec = {type: 'local', username: localuser, applicationname: localapp};
if (my_jio) { log('closing older jio'); my_jio.close(); }
log ('local storage description object: ' + JSON.stringify (spec));
my_jio = jIO.newJio(spec);
};
var newCryptJio = function () {
var user, app, pwd;
user = $('#cryptuser').attr('value');
app = $('#cryptapp').attr('value');
pwd = $('#cryptpassword').attr('value');
var spec = {type: 'crypt', username: user, password: pwd, storage:{
type: 'local', username: user, applicationname: app
}};
if (my_jio) { log('closing older jio'); my_jio.close(); }
log ('crypt storage description object: ' + JSON.stringify (spec));
my_jio = jIO.newJio(spec);
};
var newDavJio = function () {
var user, app, pwd, url;
user = $('#davuser').attr('value');
app = $('#davapp').attr('value');
pwd = $('#davpassword').attr('value');
url = $('#davurl').attr('value');
var spec = {
type: 'dav', username: user, applicationname: app,
password: pwd, url: url
};
if (my_jio) { log('closing older jio'); my_jio.close(); }
log ('dav storage description object: ' + JSON.stringify (spec));
my_jio = jIO.newJio(spec);
};
var newConflictJio = function () {
var user, app;
user = $('#conflictuser').attr('value');
app = $('#conflictapp').attr('value');
var spec = {
type: 'conflictmanager', storage: {
type: 'local', username: user, applicationname: app
}
};
if (my_jio) { log('closing older jio'); my_jio.close(); }
log ('conflict manager storage description object: '+JSON.stringify (spec));
my_jio = jIO.newJio(spec);
};
var newCustomJio = function () {
var spec = JSON.parse ($('#customstorage').text());
if (my_jio) { log('closing older jio'); my_jio.close(); }
log ('custom storage description object: '+JSON.stringify (spec));
my_jio = jIO.newJio(spec);
};
var printLocalStorage = function () {
var i;
log ('LOCALSTORAGE');
for (i in localStorage) {
log ('- '+ i +': '+localStorage[i]);
}
log ('------------------------------');
};
var callback = function (err,val,begin_date) {
log ('time : ' + (Date.now() - begin_date));
if (err) {
return error ('return :' + JSON.stringify (err));
}
log ('return : ' + JSON.stringify (val));
};
var command = function (method) {
var begin_date = Date.now(), doc = {}, opts = {};
log (method);
if (!my_jio) {
return error ('no jio set');
}
opts.conflicts = $('#show_conflicts').attr('checked')?true:false;
opts.revs = $('#show_revision_history').attr('checked')?true:false;
opts.revs_info = $('#show_revision_info').attr('checked')?true:false;
opts.max_retry = parseInt($('#max_retry').attr('value') || '0');
opts.metadata_only = $('#metadata_only').attr('checked')?true:false;
switch (method) {
case 'post':
case 'put':
doc.content = $('#content').attr('value');
doc._id = $('#filepath').attr('value');
doc._rev = $('#prev_rev').attr('value') || undefined;
break;
case 'remove':
doc._id = $('#filepath').attr('value');
opts.rev = ($('#last_revision').attr('checked')?'last':undefined) ||
$('#prev_rev').attr('value') || undefined;
break;
case 'get':
doc = $('#filepath').attr('value');
case 'allDocs':
break;
}
log ('doc: ' + JSON.stringify (doc));
log ('opts: ' + JSON.stringify (opts));
switch (method) {
case 'remove':
case 'post':
case 'put':
case 'get':
my_jio[method](doc,opts,function (err,val) {
callback(err,val,begin_date);
});
break;
case 'allDocs':
my_jio[method](opts,function (err,val) {
callback(err,val,begin_date);
});
break;
}
};
var post = function () {
command('post');
};
var put = function () {
command('put');
};
var get = function () {
command('get');
};
var remove = function () {
command('remove');
};
var allDocs = function () {
command('allDocs');
}
//-->
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>jIO Example</title>
</head>
<body>
<script type="text/javascript">
<!--
var log = function (o) {
var node = document.createElement ('div');
node.textContent = o;
document.getElementById('log').appendChild(node);
};
//-->
</script>
<div id="log">
</div>
<script type="text/javascript" src="../localorcookiestorage.js"></script>
<script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../lib/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../lib/base64/base64.js"></script>
<script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script>
<script type="text/javascript" src="../lib/jsSha2/sha2.js"></script>
<script type="text/javascript" src="../jio.storage.js"></script>
<script type="text/javascript">
<!--
var my_jio = null;
log ('Welcome to the jIO example.html!')
log ('--> Create jIO instance');
my_jio = jIO.newJio({
type: 'local', username: 'jIOtest', applicationname: 'example'
});
log ('--> put "hello" document to localStorage');
// careful ! asynchronous method
my_jio.put({_id:'hello',content:'world'},function (val) {
log ('done');
}, function (err) {
log ('error!');
});
setTimeout ( function () {
log ('--> get "hello" document from localStorage');
my_jio.get('hello',function (val) {
log ('done, content: ' + val.content);
}, function (err) {
log ('error!');
});
}, 500);
//-->
</script>
</body>
</html>
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - '+
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> Nexedi;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
concat: {
dist: {
src: ['<banner:meta.banner>',
'<file_strip_banner:../../src/<%= pkg.name %>.js>'],
dest: '../../<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: '../../<%= pkg.name %>.min.js'
}
},
qunit: {
files: []
},
lint: {
files: ['grunt.js','../../src/<%= pkg.name %>.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'lint'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
jQuery: true,
LocalOrCookieStorage: true,
Base64: true,
JIO: true,
console: true,
unescape: true,
// Needed to avoid "not defined error" with requireJs
define: true,
require: true,
// Needed to avoid "not defined error" with sinonJs
sinon: true,
module: true,
test: true,
ok: true,
deepEqual: true,
expect: true,
stop: true,
start: true,
equal: true
}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'lint concat min');
};
{
"name": "localorcookiestorage",
"title": "Local Or Cookie Storage",
"description": "A small API which can manipulate data into local persistent storage.",
"version": "0.1.0",
"homepage": "",
"author": {
"name": "Tristan Cavelier",
"email": "tristan.cavelier@tiolive.com"
},
"repository": {
"type": "git",
"url": "http://git.erp5.org/repos/ung.git"
},
"bugs": {
"url": ""
},
"licenses": [
],
"dependencies": {
},
"keywords": []
}
auto: grunt
grunt:
grunt
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - '+
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> Nexedi;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
concat: {
dist: {
src: ['<banner:meta.banner>',
'<file_strip_banner:../../src/<%= pkg.name %>/intro.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/localstorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/davstorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/replicatestorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/indexstorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/cryptstorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/conflictmanagerstorage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/outro.js>'],
dest: '../../<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: '../../<%= pkg.name %>.min.js'
}
},
qunit: {
files: []
},
lint: {
files: ['grunt.js','../../<%= pkg.name %>.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'lint'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
jQuery: true,
sjcl: true,
LocalOrCookieStorage: true,
Base64: true,
MD5: true,
hex_sha256: true,
jIO: true,
console: true,
unescape: true,
// Needed to avoid "not defined error" with requireJs
define: true,
require: true,
// Needed to avoid "not defined error" with sinonJs
sinon: true,
module: true,
test: true,
ok: true,
deepEqual: true,
expect: true,
stop: true,
start: true,
equal: true
}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'concat lint min');
};
{
"name": "jio.storage",
"title": "JIO Storage",
"description": "Javascript Input Output Storage",
"version": "0.1.0",
"homepage": "",
"author": {
"name": "Tristan Cavelier",
"email": "tristan.cavelier@tiolive.com"
},
"repository": {
"type": "git",
"url": "http://git.erp5.org/repos/ung.git"
},
"bugs": {
"url": ""
},
"licenses": [
],
"dependencies": {
"jquery": "~1.7"
},
"keywords": []
}
auto: grunt
grunt:
grunt
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - '+
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> Nexedi;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
concat: {
dist: {
src: ['<banner:meta.banner>',
// Wrapper top
'<file_strip_banner:../../src/<%= pkg.name %>/intro.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/exceptions.js>',
// Jio wrapper top
'<file_strip_banner:../../src/<%= pkg.name %>/jio.intro.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/storages/storage.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/command.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/allDocsCommand.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/getCommand.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/removeCommand.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/putCommand.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/commands/postCommand.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/jobStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/doneStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/failStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/initialStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/onGoingStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/status/waitStatus.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/job.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/announcements/announcement.js>',
// Singletons
'<file_strip_banner:../../src/<%= pkg.name %>/activityUpdater.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/announcements/announcer.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/jobIdHandler.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/jobManager.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jobs/jobRules.js>',
// Jio wrappor bottem
'<file_strip_banner:../../src/<%= pkg.name %>/jio.outro.js>',
'<file_strip_banner:../../src/<%= pkg.name %>/jioNamespace.js>',
// Wrapper bottom
'<file_strip_banner:../../src/<%= pkg.name %>/outro.js>'],
dest: '../../<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: '../../<%= pkg.name %>.min.js'
}
},
qunit: {
files: [// '../../test/jiotests.html',
'../../test/jiotests_withoutrequirejs.html']
},
lint: {
files: ['grunt.js',
'../../<%= pkg.name %>.js']
// '../../js/base64.requirejs_module.js',
// '../../src/jio.dummystorages.js',
// '../../js/jquery.requirejs_module.js',
// '../../test/jiotests.js',
// '../../test/jiotests.loader.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
LocalOrCookieStorage: true,
console: true,
unescape: true,
// Needed to avoid "not defined error" with requireJs
define: true,
require: true,
// Needed to avoid "not defined error" with sinonJs
sinon: true,
module: true,
test: true,
ok: true,
deepEqual: true,
expect: true,
stop: true,
start: true,
equal: true
}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'concat lint min');
};
{
"name": "jio",
"title": "JIO",
"description": "Javascript Input Output",
"version": "0.1.0",
"homepage": "",
"author": {
"name": "Tristan Cavelier",
"email": "tristan.cavelier@tiolive.com"
},
"repository": {
"type": "git",
"url": "http://git.erp5.org/repos/ung.git"
},
"bugs": {
"url": ""
},
"licenses": [
],
"dependencies": {
"jquery": "~1.7"
},
"keywords": []
}
#!/bin/bash
for node in `ls -1` ; do
if [ -d "$node" ] ; then
printf "\n\033[1m\033[36mmake -C %s\033[0m\n" "$node"
make -C "$node"
fi
done
/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/
var Base64 = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = Base64._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}
/*! jQuery v@1.8.0 jquery.com | jquery.org/license */
(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bX(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bV.length;while(e--){b=bV[e]+c;if(b in a)return b}return d}function bY(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function bZ(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bY(c)&&(e[f]=p._data(c,"olddisplay",cb(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b$(a,b,c){var d=bO.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function b_(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bU[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bU[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bU[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bU[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bU[e]+"Width"))||0));return f}function ca(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bP.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+b_(a,b,c||(f?"border":"content"),e)+"px"}function cb(a){if(bR[a])return bR[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bR[a]=c,c}function ch(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||cd.test(a)?d(a,e):ch(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ch(a+"["+e+"]",b[e],c,d);else d(a,b)}function cy(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cz(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cu;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cz(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cz(a,c,d,e,"*",g)),h}function cA(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cB(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cC(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cK(){try{return new a.XMLHttpRequest}catch(b){}}function cL(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cT(){return setTimeout(function(){cM=b},0),cM=p.now()}function cU(a,b){p.each(b,function(b,c){var d=(cS[b]||[]).concat(cS["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cV(a,b,c){var d,e=0,f=0,g=cR.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cM||cT(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cM||cT(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cW(k,j.opts.specialEasing);for(;e<g;e++){d=cR[e].call(j,a,k,j.opts);if(d)return d}return cU(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cW(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cX(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bY(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cb(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cO.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cY(a,b,c,d,e){return new cY.prototype.init(a,b,c,d,e)}function cZ(a,b){var c,d={height:a},e=0;for(;e<4;e+=2-b)c=bU[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function c_(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=r.test(" ")?/^[\s\xA0]+|[\s\xA0]+$/g:/^\s+|\s+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.0",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":a.toString().replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||f.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete"||e.readyState!=="loading"&&e.addEventListener)setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){p.isFunction(c)&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/^(?:\{.*\}|\[.*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")===0&&(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.shift(),e=p._queueHooks(a,b),f=function(){p.dequeue(a,b)};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),delete e.stop,d.call(a,f,e)),!c.length&&e&&e.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)(d=p._data(g[h],a+"queueHooks"))&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)~f.indexOf(" "+b[g]+" ")||(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,k,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=[].slice.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click")){g=p(this),g.context=this;for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){i={},k=[],g[0]=f;for(d=0;d<q;d++)l=o[d],m=l.selector,i[m]===b&&(i[m]=g.is(m)),i[m]&&k.push(l);k.length&&u.push({elem:f,matches:k})}}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){j=u[d],c.currentTarget=j.elem;for(e=0;e<j.matches.length&&!c.isImmediatePropagationStopped();e++){l=j.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,h=((p.event.special[l.origType]||{}).handle||l.handler).apply(j.elem,r),h!==b&&(c.result=h,h===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{ready:{setup:p.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bd(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)Z(a,b[e],c,d)}function be(a,b,c,d,e,f){var g,h=$.setFilters[b.toLowerCase()];return h||Z.error(b),(a||!(g=e))&&bd(a||"*",d,g=[],e),g.length>0?h(g,c,f):[]}function bf(a,c,d,e,f){var g,h,i,j,k,l,m,n,p=0,q=f.length,s=L.POS,t=new RegExp("^"+s.source+"(?!"+r+")","i"),u=function(){var a=1,c=arguments.length-2;for(;a<c;a++)arguments[a]===b&&(g[a]=b)};for(;p<q;p++){s.exec(""),a=f[p],j=[],i=0,k=e;while(g=s.exec(a)){n=s.lastIndex=g.index+g[0].length;if(n>i){m=a.slice(i,g.index),i=n,l=[c],B.test(m)&&(k&&(l=k),k=e);if(h=H.test(m))m=m.slice(0,-5).replace(B,"$&*");g.length>1&&g[0].replace(t,u),k=be(m,g[1],g[2],l,k,h)}}k?(j=j.concat(k),(m=a.slice(i))&&m!==")"?B.test(m)?bd(m,j,d,e):Z(m,c,d,e?e.concat(k):k):o.apply(d,j)):Z(a,c,d,e)}return q===1?d:Z.uniqueSort(d)}function bg(a,b,c){var d,e,f,g=[],i=0,j=D.exec(a),k=!j.pop()&&!j.pop(),l=k&&a.match(C)||[""],m=$.preFilter,n=$.filter,o=!c&&b!==h;for(;(e=l[i])!=null&&k;i++){g.push(d=[]),o&&(e=" "+e);while(e){k=!1;if(j=B.exec(e))e=e.slice(j[0].length),k=d.push({part:j.pop().replace(A," "),captures:j});for(f in n)(j=L[f].exec(e))&&(!m[f]||(j=m[f](j,b,c)))&&(e=e.slice(j.shift().length),k=d.push({part:f,captures:j}));if(!k)break}}return k||Z.error(a),g}function bh(a,b,e){var f=b.dir,g=m++;return a||(a=function(a){return a===e}),b.first?function(b,c){while(b=b[f])if(b.nodeType===1)return a(b,c)&&b}:function(b,e){var h,i=g+"."+d,j=i+"."+c;while(b=b[f])if(b.nodeType===1){if((h=b[q])===j)return b.sizset;if(typeof h=="string"&&h.indexOf(i)===0){if(b.sizset)return b}else{b[q]=j;if(a(b,e))return b.sizset=!0,b;b.sizset=!1}}}}function bi(a,b){return a?function(c,d){var e=b(c,d);return e&&a(e===!0?c:e,d)}:b}function bj(a,b,c){var d,e,f=0;for(;d=a[f];f++)$.relative[d.part]?e=bh(e,$.relative[d.part],b):(d.captures.push(b,c),e=bi(e,$.filter[d.part].apply(null,d.captures)));return e}function bk(a){return function(b,c){var d,e=0;for(;d=a[e];e++)if(d(b,c))return!0;return!1}}var c,d,e,f,g,h=a.document,i=h.documentElement,j="undefined",k=!1,l=!0,m=0,n=[].slice,o=[].push,q=("sizcache"+Math.random()).replace(".",""),r="[\\x20\\t\\r\\n\\f]",s="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",t=s.replace("w","w#"),u="([*^$|!~]?=)",v="\\["+r+"*("+s+")"+r+"*(?:"+u+r+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+t+")|)|)"+r+"*\\]",w=":("+s+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",x=":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",y=r+"*([\\x20\\t\\r\\n\\f>+~])"+r+"*",z="(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|"+v+"|"+w.replace(2,7)+"|[^\\\\(),])+",A=new RegExp("^"+r+"+|((?:^|[^\\\\])(?:\\\\.)*)"+r+"+$","g"),B=new RegExp("^"+y),C=new RegExp(z+"?(?="+r+"*,|$)","g"),D=new RegExp("^(?:(?!,)(?:(?:^|,)"+r+"*"+z+")*?|"+r+"*(.*?))(\\)|$)"),E=new RegExp(z.slice(19,-6)+"\\x20\\t\\r\\n\\f>+~])+|"+y,"g"),F=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,G=/[\x20\t\r\n\f]*[+~]/,H=/:not\($/,I=/h\d/i,J=/input|select|textarea|button/i,K=/\\(?!\\)/g,L={ID:new RegExp("^#("+s+")"),CLASS:new RegExp("^\\.("+s+")"),NAME:new RegExp("^\\[name=['\"]?("+s+")['\"]?\\]"),TAG:new RegExp("^("+s.replace("[-","[-\\*")+")"),ATTR:new RegExp("^"+v),PSEUDO:new RegExp("^"+w),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+r+"*(even|odd|(([+-]|)(\\d*)n|)"+r+"*(?:([+-]|)"+r+"*(\\d+)|))"+r+"*\\)|)","i"),POS:new RegExp(x,"ig"),needsContext:new RegExp("^"+r+"*[>+~]|"+x,"i")},M={},N=[],O={},P=[],Q=function(a){return a.sizzleFilter=!0,a},R=function(a){return function(b){return b.nodeName.toLowerCase()==="input"&&b.type===a}},S=function(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}},T=function(a){var b=!1,c=h.createElement("div");try{b=a(c)}catch(d){}return c=null,b},U=T(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),V=T(function(a){a.id=q+0,a.innerHTML="<a name='"+q+"'></a><div name='"+q+"'></div>",i.insertBefore(a,i.firstChild);var b=h.getElementsByName&&h.getElementsByName(q).length===2+h.getElementsByName(q+0).length;return g=!h.getElementById(q),i.removeChild(a),b}),W=T(function(a){return a.appendChild(h.createComment("")),a.getElementsByTagName("*").length===0}),X=T(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==j&&a.firstChild.getAttribute("href")==="#"}),Y=T(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||a.getElementsByClassName("e").length===0?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length!==1)}),Z=function(a,b,c,d){c=c||[],b=b||h;var e,f,g,i,j=b.nodeType;if(j!==1&&j!==9)return[];if(!a||typeof a!="string")return c;g=ba(b);if(!g&&!d)if(e=F.exec(a))if(i=e[1]){if(j===9){f=b.getElementById(i);if(!f||!f.parentNode)return c;if(f.id===i)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(i))&&bb(b,f)&&f.id===i)return c.push(f),c}else{if(e[2])return o.apply(c,n.call(b.getElementsByTagName(a),0)),c;if((i=e[3])&&Y&&b.getElementsByClassName)return o.apply(c,n.call(b.getElementsByClassName(i),0)),c}return bm(a,b,c,d,g)},$=Z.selectors={cacheLength:50,match:L,order:["ID","TAG"],attrHandle:{},createPseudo:Q,find:{ID:g?function(a,b,c){if(typeof b.getElementById!==j&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==j&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==j&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:W?function(a,b){if(typeof b.getElementsByTagName!==j)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(K,""),a[3]=(a[4]||a[5]||"").replace(K,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||Z.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&Z.error(a[0]),a},PSEUDO:function(a){var b,c=a[4];return L.CHILD.test(a[0])?null:(c&&(b=D.exec(c))&&b.pop()&&(a[0]=a[0].slice(0,b[0].length-c.length-1),c=b[0].slice(0,-1)),a.splice(2,3,c||a[3]),a)}},filter:{ID:g?function(a){return a=a.replace(K,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(K,""),function(b){var c=typeof b.getAttributeNode!==j&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(K,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=M[a];return b||(b=M[a]=new RegExp("(^|"+r+")"+a+"("+r+"|$)"),N.push(a),N.length>$.cacheLength&&delete M[N.shift()]),function(a){return b.test(a.className||typeof a.getAttribute!==j&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=Z.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return Z.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=m++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[q]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[q]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e=$.pseudos[a]||$.pseudos[a.toLowerCase()];return e||Z.error("unsupported pseudo: "+a),e.sizzleFilter?e(b,c,d):e}},pseudos:{not:Q(function(a,b,c){var d=bl(a.replace(A,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!$.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:Q(function(a){return function(b){return(b.textContent||b.innerText||bc(b)).indexOf(a)>-1}}),has:Q(function(a){return function(b){return Z(a,b).length>0}}),header:function(a){return I.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:R("radio"),checkbox:R("checkbox"),file:R("file"),password:R("password"),image:R("image"),submit:S("submit"),reset:S("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return J.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},odd:function(a,b,c){var d=[],e=c?0:1,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},lt:function(a,b,c){return c?a.slice(+b):a.slice(0,+b)},gt:function(a,b,c){return c?a.slice(0,+b+1):a.slice(+b+1)},eq:function(a,b,c){var d=a.splice(+b,1);return c?a:d}}};$.setFilters.nth=$.setFilters.eq,$.filters=$.pseudos,X||($.attrHandle={href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}}),V&&($.order.push("NAME"),$.find.NAME=function(a,b){if(typeof b.getElementsByName!==j)return b.getElementsByName(a)}),Y&&($.order.splice(1,0,"CLASS"),$.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!==j&&!c)return b.getElementsByClassName(a)});try{n.call(i.childNodes,0)[0].nodeType}catch(_){n=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}var ba=Z.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},bb=Z.contains=i.compareDocumentPosition?function(a,b){return!!(a.compareDocumentPosition(b)&16)}:i.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc=Z.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=bc(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=bc(b);return c};Z.attr=function(a,b){var c,d=ba(a);return d||(b=b.toLowerCase()),$.attrHandle[b]?$.attrHandle[b](a):U||d?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},Z.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},[0,0].sort(function(){return l=0}),i.compareDocumentPosition?e=function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:(e=function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],g=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return f(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)g.unshift(j),j=j.parentNode;c=e.length,d=g.length;for(var l=0;l<c&&l<d;l++)if(e[l]!==g[l])return f(e[l],g[l]);return l===c?f(a,g[l],-1):f(e[l],b,1)},f=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),Z.uniqueSort=function(a){var b,c=1;if(e){k=l,a.sort(e);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1)}return a};var bl=Z.compile=function(a,b,c){var d,e,f,g=O[a];if(g&&g.context===b)return g;e=bg(a,b,c);for(f=0;d=e[f];f++)e[f]=bj(d,b,c);return g=O[a]=bk(e),g.context=b,g.runs=g.dirruns=0,P.push(a),P.length>$.cacheLength&&delete O[P.shift()],g};Z.matches=function(a,b){return Z(a,null,null,b)},Z.matchesSelector=function(a,b){return Z(b,null,null,[a]).length>0};var bm=function(a,b,e,f,g){a=a.replace(A,"$1");var h,i,j,k,l,m,p,q,r,s=a.match(C),t=a.match(E),u=b.nodeType;if(L.POS.test(a))return bf(a,b,e,f,s);if(f)h=n.call(f,0);else if(s&&s.length===1){if(t.length>1&&u===9&&!g&&(s=L.ID.exec(t[0]))){b=$.find.ID(s[1],b,g)[0];if(!b)return e;a=a.slice(t.shift().length)}q=(s=G.exec(t[0]))&&!s.index&&b.parentNode||b,r=t.pop(),m=r.split(":not")[0];for(j=0,k=$.order.length;j<k;j++){p=$.order[j];if(s=L[p].exec(m)){h=$.find[p]((s[1]||"").replace(K,""),q,g);if(h==null)continue;m===r&&(a=a.slice(0,a.length-r.length)+m.replace(L[p],""),a||o.apply(e,n.call(h,0)));break}}}if(a){i=bl(a,b,g),d=i.dirruns++,h==null&&(h=$.find.TAG("*",G.test(a)&&b.parentNode||b));for(j=0;l=h[j];j++)c=i.runs++,i(l,b)&&e.push(l)}return e};h.querySelectorAll&&function(){var a,b=bm,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[],f=[":active"],g=i.matchesSelector||i.mozMatchesSelector||i.webkitMatchesSelector||i.oMatchesSelector||i.msMatchesSelector;T(function(a){a.innerHTML="<select><option selected></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+r+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+r+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bm=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return o.apply(f,n.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j=d.getAttribute("id"),k=j||q,l=G.test(a)&&d.parentNode||d;j?k=k.replace(c,"\\$&"):d.setAttribute("id",k);try{return o.apply(f,n.call(l.querySelectorAll(a.replace(C,"[id='"+k+"'] $&")),0)),f}catch(i){}finally{j||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push($.match.PSEUDO)}catch(c){}}),f=new RegExp(f.join("|")),Z.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!ba(b)&&!f.test(c)&&(!e||!e.test(c)))try{var h=g.call(b,c);if(h||a||b.document&&b.document.nodeType!==11)return h}catch(i){}return Z(c,null,null,[b]).length>0})}(),Z.attr=p.attr,p.find=Z,p.expr=Z.selectors,p.expr[":"]=p.expr.pseudos,p.unique=Z.uniqueSort,p.text=Z.getText,p.isXMLDoc=Z.isXML,p.contains=Z.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=(c[0]||c).ownerDocument||c[0]||c,typeof c.createDocumentFragment=="undefined"&&(c=e),a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=0,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(g=b===e&&bA;(h=a[s])!=null;s++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{g=g||bk(b),l=l||g.appendChild(b.createElement("div")),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(f=n.length-1;f>=0;--f)p.nodeName(n[f],"tbody")&&!n[f].childNodes.length&&n[f].parentNode.removeChild(n[f])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l=g.lastChild}h.nodeType?t.push(h):t=p.merge(t,h)}l&&(g.removeChild(l),h=l=g=null);if(!p.support.appendChecked)for(s=0;(h=t[s])!=null;s++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(s=0;(h=t[s])!=null;s++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[s+1,0].concat(r)),s+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^margin/,bO=new RegExp("^("+q+")(.*)$","i"),bP=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bQ=new RegExp("^([-+])=("+q+")","i"),bR={},bS={position:"absolute",visibility:"hidden",display:"block"},bT={letterSpacing:0,fontWeight:400,lineHeight:1},bU=["Top","Right","Bottom","Left"],bV=["Webkit","O","Moz","ms"],bW=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return bZ(this,!0)},hide:function(){return bZ(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bW.apply(this,arguments):this.each(function(){(c?a:bY(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bX(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bQ.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bX(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bT&&(f=bT[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(a,b){var c,d,e,f,g=getComputedStyle(a,null),h=a.style;return g&&(c=g[b],c===""&&!p.contains(a.ownerDocument.documentElement,a)&&(c=p.style(a,b)),bP.test(c)&&bN.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=c,c=g.width,h.width=d,h.minWidth=e,h.maxWidth=f)),c}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bP.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0||bH(a,"display")!=="none"?ca(a,b,d):p.swap(a,bS,function(){return ca(a,b,d)})},set:function(a,c,d){return b$(a,c,d?b_(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bP.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bU[d]+b]=e[d]||e[d-2]||e[0];return f}},bN.test(a)||(p.cssHooks[a+b].set=b$)});var cc=/%20/g,cd=/\[\]$/,ce=/\r?\n/g,cf=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,cg=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||cg.test(this.nodeName)||cf.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(ce,"\r\n")}}):{name:b.name,value:c.replace(ce,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ch(d,a[d],c,f);return e.join("&").replace(cc,"+")};var ci,cj,ck=/#.*$/,cl=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cm=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,cn=/^(?:GET|HEAD)$/,co=/^\/\//,cp=/\?/,cq=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cr=/([?&])_=[^&]*/,cs=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,ct=p.fn.load,cu={},cv={},cw=["*/"]+["*"];try{ci=f.href}catch(cx){ci=e.createElement("a"),ci.href="",ci=ci.href}cj=cs.exec(ci.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&ct)return ct.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cq,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cA(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cA(a,b),a},ajaxSettings:{url:ci,isLocal:cm.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cw},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cy(cu),ajaxTransport:cy(cv),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cB(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cC(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cl.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(ck,"").replace(co,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=cs.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==cj[1]&&i[2]==cj[2]&&(i[3]||(i[1]==="http:"?80:443))==(cj[3]||(cj[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cz(cu,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!cn.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cp.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cr,"$1_="+z);l.url=A+(A===l.url?(cp.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cw+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cz(cv,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cD=[],cE=/\?/,cF=/(=)\?(?=&|$)|\?\?/,cG=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cD.pop()||p.expando+"_"+cG++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cF.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cF.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cF,"$1"+f):m?c.data=i.replace(cF,"$1"+f):k&&(c.url+=(cE.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cD.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cH,cI=a.ActiveXObject?function(){for(var a in cH)cH[a](0,1)}:!1,cJ=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cK()||cL()}:cK,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cI&&delete cH[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cJ,cI&&(cH||(cH={},p(a).unload(cI)),cH[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cM,cN,cO=/^(?:toggle|show|hide)$/,cP=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cQ=/queueHooks$/,cR=[cX],cS={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cP.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cV,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cS[c]=cS[c]||[],cS[c].unshift(b)},prefilter:function(a,b){b?cR.unshift(a):cR.push(a)}}),p.Tween=cY,cY.prototype={constructor:cY,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cY.propHooks[this.prop];return a&&a.get?a.get(this):cY.propHooks._default.get(this)},run:function(a){var b,c=cY.propHooks[this.prop];return this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration),this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cY.propHooks._default.set(this),this}},cY.prototype.init.prototype=cY.prototype,cY.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cY.propHooks.scrollTop=cY.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(cZ(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bY).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cV(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cQ.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:cZ("show"),slideUp:cZ("hide"),slideToggle:cZ("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cY.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cN&&(cN=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cN),cN=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c$=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j,k,l,m=this[0],n=m&&m.ownerDocument;if(!n)return;return(e=n.body)===m?p.offset.bodyOffset(m):(d=n.documentElement,p.contains(d,m)?(c=m.getBoundingClientRect(),f=c_(n),g=d.clientTop||e.clientTop||0,h=d.clientLeft||e.clientLeft||0,i=f.pageYOffset||d.scrollTop,j=f.pageXOffset||d.scrollLeft,k=c.top+i-g,l=c.left+j-h,{top:k,left:l}):{top:0,left:0})},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c$.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c$.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=c_(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file
12/21/2004 - version 0.3
- Fixed copyright notice
- Included license.txt file
- Added readme.txt
- Released to the public
04/09/2003 - version 0.2
- sha256 rewritten ussing some code and ideas
from Paul Johnston's sha1.js
- Now is possible to get the hash as a string,
base 64 encoded or hex encoded.
- It can now calculate the hash from unicode
strings.
- Hexadecimal hash can be lowercase (default)
or uppercase.
- Optimized the rest of the code.
- Written the test page with the test vectors from
nist paper.
- Tested on OE 6.0 and OE 6.0SP1.
04/08/2003 - version 0.1
- First sha256 implementation. It's a dirty one, but works.
- Tested on OE 6.0SP1
\ No newline at end of file
jsSHA2 - OpenSource JavaScript implementation of the Secure Hash Algorithms,
SHA-256-384-512 - http://anmar.eu.org/projects/jssha2/
Introduction
--------------------------------
jsSHA2 is an OpenSource JavaScript implementation of the Secure Hash Algorithm,
SHA-256-384-512. As defined by NIST:
'All of the algorithms are iterative, one-way hash functions that can process a
message to produce a condensed representation called a message digest. These
algorithms enable the determination of a message’s integrity: any change to the
message will, with a very high probability, result in a different message digest.
This property is useful in the generation and verification of digital signatures
and message authentication codes, and in the generation of random numbers (bits)'
File description
--------------------------------
sha2.js:
Full implementation, it gives the hash as a string, base64 encoded or hex encoded.
sha256.js:
Is a stripped down version for using only SHA-256 in web based apps. It only gives
hex output.
Features
--------------------------------
There is a working implementation of SHA-256.
Instructions
--------------------------------
Reference the appropriate file from your page:
<script type="text/javascript" src="sha256.js"></script>
Then, use it:
<script language="JavaScript">
hash = hex_sha256("test string");
</script>
Authors
--------------------------------
- Angel Marin <anmar at gmx.net> - http://anmar.eu.org/
License
--------------------------------
- Read the included license.txt
--------------------------------
Angel Marin 2003 - 2004 - http://anmar.eu.org/
Copyright (c) 2003-2004, Angel Marin
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the <ORGANIZATION> nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
/* A JavaScript implementation of the Secure Hash Standard
* Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
* Distributed under the BSD License
* Some bits taken from Paul Johnston's SHA-1 implementation
*/
(function () {
var chrsz = 8;/* bits per input character. 8 - ASCII; 16 - Unicode */
var hexcase = 0;/* hex output format. 0 - lowercase; 1 - uppercase */
function safe_add (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
function R (X, n) {return ( X >>> n );}
function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
function Sigma0512(x) {return (S(x, 28) ^ S(x, 34) ^ S(x, 39));}
function Sigma1512(x) {return (S(x, 14) ^ S(x, 18) ^ S(x, 41));}
function Gamma0512(x) {return (S(x, 1) ^ S(x, 8) ^ R(x, 7));}
function Gamma1512(x) {return (S(x, 19) ^ S(x, 61) ^ R(x, 6));}
function newArray (n) {
var a = [];
for (;n>0;n--) {
a.push(undefined);
}
return a;
}
function core_sha256 (m, l) {
var K = [0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2];
var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
var W = newArray(64);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
/* append padding */
m[l >> 5] |= 0x80 << (24 - l % 32);
m[((l + 64 >> 9) << 4) + 15] = l;
for ( var i = 0; i<m.length; i+=16 ) {
a = HASH[0];
b = HASH[1];
c = HASH[2];
d = HASH[3];
e = HASH[4];
f = HASH[5];
g = HASH[6];
h = HASH[7];
for ( var j = 0; j<64; j++) {
if (j < 16) {
W[j] = m[j + i];
} else {
W[j] = safe_add(safe_add(safe_add(Gamma1256(
W[j - 2]),W[j - 7]),Gamma0256(W[j - 15])), W[j - 16]);
}
T1 = safe_add(safe_add(safe_add(safe_add(
h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
HASH[0] = safe_add(a, HASH[0]);
HASH[1] = safe_add(b, HASH[1]);
HASH[2] = safe_add(c, HASH[2]);
HASH[3] = safe_add(d, HASH[3]);
HASH[4] = safe_add(e, HASH[4]);
HASH[5] = safe_add(f, HASH[5]);
HASH[6] = safe_add(g, HASH[6]);
HASH[7] = safe_add(h, HASH[7]);
}
return HASH;
}
function core_sha512 (m, l) {
var K = [0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817];
var HASH = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179];
var W = newArray(80);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
}
function str2binb (str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
return bin;
}
function binb2str (bin) {
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask);
return str;
}
function binb2hex (binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
function binb2b64 (binarray) {
var tab =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3) {
var triplet = (((binarray[i >> 2] >> 8 * (3- i %4)) & 0xFF) << 16) |
(((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) |
((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++) {
if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
else {str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);}
}
}
return str;
}
function hex_sha256(s){
return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
}
function b64_sha256(s){
return binb2b64(core_sha256(str2binb(s),s.length * chrsz));
}
function str_sha256(s){
return binb2str(core_sha256(str2binb(s),s.length * chrsz));
}
window.hex_sha256 = hex_sha256;
window.b64_sha256 = b64_sha256;
window.str_sha256 = str_sha256;
}());
/* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
* Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
* Distributed under the BSD License
* Some bits taken from Paul Johnston's SHA-1 implementation
*/
(function () {
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
function safe_add (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
function R (X, n) {return ( X >>> n );}
function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
function newArray (n) {
var a = [];
for (;n>0;n--) {
a.push(undefined);
}
return a;
}
function core_sha256 (m, l) {
var K = [0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2];
var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
var W = newArray(64);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
/* append padding */
m[l >> 5] |= 0x80 << (24 - l % 32);
m[((l + 64 >> 9) << 4) + 15] = l;
for ( var i = 0; i<m.length; i+=16 ) {
a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3];
e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
for ( var j = 0; j<64; j++) {
if (j < 16) {
W[j] = m[j + i];
} else {
W[j] = safe_add(safe_add(safe_add(Gamma1256(
W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
}
T1 = safe_add(safe_add(safe_add(
safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
h = g; g = f; f = e; e = safe_add(d, T1);
d = c; c = b; b = a; a = safe_add(T1, T2);
}
HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]);
HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]);
HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
}
return HASH;
}
function str2binb (str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
return bin;
}
function binb2hex (binarray) {
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
function hex_sha256(s){
return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
}
window.hex_sha256 = hex_sha256;
}());
<html>
<head>
<title>anmar.eu.org - CTNjsSha2: sha2 test page</title>
<script language="JavaScript" src="../sha2.js"></script>
</head>
<body onload="testSHA2()">
<form name="testform256">
<input type="text" name="orig256" size="75" value="abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" onchange="testSHA256()"><br>
<input type="text" name="calc256" size="75" value="" disabled="true"><br>
<input type="text" name="control256" size="75" value="248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" onchange="testSHA256()"><br>
<input type="text" name="result256" size="15" value="Testing...">
</form>
<script language="JavaScript">
function testSHA2 () {
testSHA256();
}
function testSHA256 () {
document.testform256.calc256.value = hex_sha256 (document.testform256.orig256.value);
if (document.testform256.calc256.value == document.testform256.control256.value) {
document.testform256.result256.value="OK";
} else {
document.testform256.result256.value="Error";
}
}
</script>
</body>
</html>
/**
* QUnit v1.5.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-header label {
display: inline-block;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}
/**
* QUnit v1.5.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
(function(window) {
var defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem(x, x);
sessionStorage.removeItem(x);
return true;
} catch(e) {
return false;
}
}())
};
var testId = 0,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
var Test = function(name, testName, expected, async, callback) {
this.name = name;
this.testName = testName;
this.expected = expected;
this.async = async;
this.callback = callback;
this.assertions = [];
};
Test.prototype = {
init: function() {
var tests = id("qunit-tests");
if (tests) {
var b = document.createElement("strong");
b.innerHTML = "Running " + this.name;
var li = document.createElement("li");
li.appendChild( b );
li.className = "running";
li.id = this.id = "test-output" + testId++;
tests.appendChild( li );
}
},
setup: function() {
if (this.module != config.previousModule) {
if ( config.previousModule ) {
runLoggingCallbacks('moduleDone', QUnit, {
name: config.previousModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
} );
}
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0 };
runLoggingCallbacks( 'moduleStart', QUnit, {
name: this.module
} );
} else if (config.autorun) {
runLoggingCallbacks( 'moduleStart', QUnit, {
name: this.module
} );
}
config.current = this;
this.testEnvironment = extend({
setup: function() {},
teardown: function() {}
}, this.moduleTestEnvironment);
runLoggingCallbacks( 'testStart', QUnit, {
name: this.testName,
module: this.module
});
// allow utility functions to access the current test environment
// TODO why??
QUnit.current_testEnvironment = this.testEnvironment;
if ( !config.pollution ) {
saveGlobal();
}
if ( config.notrycatch ) {
this.testEnvironment.setup.call(this.testEnvironment);
return;
}
try {
this.testEnvironment.setup.call(this.testEnvironment);
} catch(e) {
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
}
},
run: function() {
config.current = this;
var running = id("qunit-testresult");
if ( running ) {
running.innerHTML = "Running: <br/>" + this.name;
}
if ( this.async ) {
QUnit.stop();
}
if ( config.notrycatch ) {
this.callback.call(this.testEnvironment);
return;
}
try {
this.callback.call(this.testEnvironment);
} catch(e) {
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
QUnit.start();
}
}
},
teardown: function() {
config.current = this;
if ( config.notrycatch ) {
this.testEnvironment.teardown.call(this.testEnvironment);
return;
} else {
try {
this.testEnvironment.teardown.call(this.testEnvironment);
} catch(e) {
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
}
}
checkPollution();
},
finish: function() {
config.current = this;
if ( this.expected != null && this.expected != this.assertions.length ) {
QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
} else if ( this.expected == null && !this.assertions.length ) {
QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." );
}
var good = 0, bad = 0,
li, i,
tests = id("qunit-tests");
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
if ( tests ) {
var ol = document.createElement("ol");
for ( i = 0; i < this.assertions.length; i++ ) {
var assertion = this.assertions[i];
li = document.createElement("li");
li.className = assertion.result ? "pass" : "fail";
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
ol.appendChild( li );
if ( assertion.result ) {
good++;
} else {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
if (bad) {
sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad);
} else {
sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName);
}
}
if (bad === 0) {
ol.style.display = "none";
}
var b = document.createElement("strong");
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
var a = document.createElement("a");
a.innerHTML = "Rerun";
a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
addEvent(b, "click", function() {
var next = b.nextSibling.nextSibling,
display = next.style.display;
next.style.display = display === "none" ? "block" : "none";
});
addEvent(b, "dblclick", function(e) {
var target = e && e.target ? e.target : window.event.srcElement;
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
target = target.parentNode;
}
if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
}
});
li = id(this.id);
li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild );
li.appendChild( b );
li.appendChild( a );
li.appendChild( ol );
} else {
for ( i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[i].result ) {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
}
QUnit.reset();
runLoggingCallbacks( 'testDone', QUnit, {
name: this.testName,
module: this.module,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length
} );
},
queue: function() {
var test = this;
synchronize(function() {
test.init();
});
function run() {
// each of these can by async
synchronize(function() {
test.setup();
});
synchronize(function() {
test.run();
});
synchronize(function() {
test.teardown();
});
synchronize(function() {
test.finish();
});
}
// defer when previous test run passed, if storage is available
var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);
if (bad) {
run();
} else {
synchronize(run, true);
}
}
};
var QUnit = {
// call on start of module test to prepend name to all tests
module: function(name, testEnvironment) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
},
asyncTest: function(testName, expected, callback) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test(testName, expected, callback, true);
},
test: function(testName, expected, callback, async) {
var name = '<span class="test-name">' + escapeInnerText(testName) + '</span>';
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
if ( config.currentModule ) {
name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
}
if ( !validTest(config.currentModule + ": " + testName) ) {
return;
}
var test = new Test(name, testName, expected, async, callback);
test.module = config.currentModule;
test.moduleTestEnvironment = config.currentModuleTestEnviroment;
test.queue();
},
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function(asserts) {
config.current.expected = asserts;
},
// Asserts true.
// @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
ok: function(result, msg) {
if (!config.current) {
throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
}
result = !!result;
var details = {
result: result,
message: msg
};
msg = escapeInnerText(msg || (result ? "okay" : "failed"));
if ( !result ) {
var source = sourceFromStacktrace(2);
if (source) {
details.source = source;
msg += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
}
}
runLoggingCallbacks( 'log', QUnit, details );
config.current.assertions.push({
result: result,
message: msg
});
},
// Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values.
// @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
equal: function(actual, expected, message) {
QUnit.push(expected == actual, actual, expected, message);
},
notEqual: function(actual, expected, message) {
QUnit.push(expected != actual, actual, expected, message);
},
deepEqual: function(actual, expected, message) {
QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
},
notDeepEqual: function(actual, expected, message) {
QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
},
strictEqual: function(actual, expected, message) {
QUnit.push(expected === actual, actual, expected, message);
},
notStrictEqual: function(actual, expected, message) {
QUnit.push(expected !== actual, actual, expected, message);
},
raises: function(block, expected, message) {
var actual, ok = false;
if (typeof expected === 'string') {
message = expected;
expected = null;
}
try {
block.call(config.current.testEnvironment);
} catch (e) {
actual = e;
}
if (actual) {
// we don't want to validate thrown error
if (!expected) {
ok = true;
// expected is a regexp
} else if (QUnit.objectType(expected) === "regexp") {
ok = expected.test(actual);
// expected is a constructor
} else if (actual instanceof expected) {
ok = true;
// expected is a validation function which returns true is validation passed
} else if (expected.call({}, actual) === true) {
ok = true;
}
}
QUnit.ok(ok, message);
},
start: function(count) {
config.semaphore -= count || 1;
if (config.semaphore > 0) {
// don't start until equal number of stop-calls
return;
}
if (config.semaphore < 0) {
// ignore if start is called more often then stop
config.semaphore = 0;
}
// A slight delay, to avoid any current callbacks
if ( defined.setTimeout ) {
window.setTimeout(function() {
if (config.semaphore > 0) {
return;
}
if ( config.timeout ) {
clearTimeout(config.timeout);
}
config.blocking = false;
process(true);
}, 13);
} else {
config.blocking = false;
process(true);
}
},
stop: function(count) {
config.semaphore += count || 1;
config.blocking = true;
if ( config.testTimeout && defined.setTimeout ) {
clearTimeout(config.timeout);
config.timeout = window.setTimeout(function() {
QUnit.ok( false, "Test timed out" );
config.semaphore = 1;
QUnit.start();
}, config.testTimeout);
}
}
};
//We want access to the constructor's prototype
(function() {
function F(){}
F.prototype = QUnit;
QUnit = new F();
//Make F QUnit's constructor so that we can add to the prototype later
QUnit.constructor = F;
}());
// deprecated; still export them to window to provide clear error messages
// next step: remove entirely
QUnit.equals = function() {
QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
};
QUnit.same = function() {
QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
};
// Maintain internal state
var config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// when enabled, show only failing tests
// gets persisted through sessionStorage and can be changed in UI via checkbox
hidepassed: false,
// by default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder: true,
// by default, modify document.title when suite is done
altertitle: true,
urlConfig: ['noglobals', 'notrycatch'],
//logging callback queues
begin: [],
done: [],
log: [],
testStart: [],
testDone: [],
moduleStart: [],
moduleDone: []
};
// Load paramaters
(function() {
var location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {},
current;
if ( params[ 0 ] ) {
for ( var i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
urlParams[ current[ 0 ] ] = current[ 1 ];
}
}
QUnit.urlParams = urlParams;
config.filter = urlParams.filter;
// Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === 'file:';
}());
// Expose the API as global variables, unless an 'exports'
// object exists, in that case we assume we're in CommonJS - export everything at the end
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
extend(window, QUnit);
window.QUnit = QUnit;
}
// define these after exposing globals to keep them in these QUnit namespace only
extend(QUnit, {
config: config,
// Initialize the configuration options
init: function() {
extend(config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: +new Date(),
updateRate: 1000,
blocking: false,
autostart: true,
autorun: false,
filter: "",
queue: [],
semaphore: 0
});
var qunit = id( "qunit" );
if ( qunit ) {
qunit.innerHTML =
'<h1 id="qunit-header">' + escapeInnerText( document.title ) + '</h1>' +
'<h2 id="qunit-banner"></h2>' +
'<div id="qunit-testrunner-toolbar"></div>' +
'<h2 id="qunit-userAgent"></h2>' +
'<ol id="qunit-tests"></ol>';
}
var tests = id( "qunit-tests" ),
banner = id( "qunit-banner" ),
result = id( "qunit-testresult" );
if ( tests ) {
tests.innerHTML = "";
}
if ( banner ) {
banner.className = "";
}
if ( result ) {
result.parentNode.removeChild( result );
}
if ( tests ) {
result = document.createElement( "p" );
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
result.innerHTML = 'Running...<br/>&nbsp;';
}
},
// Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() {
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
var main = id( 'qunit-fixture' );
if ( main ) {
main.innerHTML = config.fixture;
}
}
},
// Trigger an event on an element.
// @example triggerEvent( document.body, "click" );
triggerEvent: function( elem, type, event ) {
if ( document.createEvent ) {
event = document.createEvent("MouseEvents");
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent( event );
} else if ( elem.fireEvent ) {
elem.fireEvent("on"+type);
}
},
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) == type;
},
objectType: function( obj ) {
if (typeof obj === "undefined") {
return "undefined";
// consider: typeof null === object
}
if (obj === null) {
return "null";
}
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || '';
switch (type) {
case 'Number':
if (isNaN(obj)) {
return "nan";
}
return "number";
case 'String':
case 'Boolean':
case 'Array':
case 'Date':
case 'RegExp':
case 'Function':
return type.toLowerCase();
}
if (typeof obj === "object") {
return "object";
}
return undefined;
},
push: function(result, actual, expected, message) {
if (!config.current) {
throw new Error("assertion outside test context, was " + sourceFromStacktrace());
}
var details = {
result: result,
message: message,
actual: actual,
expected: expected
};
message = escapeInnerText(message) || (result ? "okay" : "failed");
message = '<span class="test-message">' + message + "</span>";
var output = message;
if (!result) {
expected = escapeInnerText(QUnit.jsDump.parse(expected));
actual = escapeInnerText(QUnit.jsDump.parse(actual));
output += '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
if (actual != expected) {
output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
}
var source = sourceFromStacktrace();
if (source) {
details.source = source;
output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
}
output += "</table>";
}
runLoggingCallbacks( 'log', QUnit, details );
config.current.assertions.push({
result: !!result,
message: output
});
},
pushFailure: function(message, source) {
var details = {
result: false,
message: message
};
var output = escapeInnerText(message);
if (source) {
details.source = source;
output += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
}
runLoggingCallbacks( 'log', QUnit, details );
config.current.assertions.push({
result: false,
message: output
});
},
url: function( params ) {
params = extend( extend( {}, QUnit.urlParams ), params );
var querystring = "?",
key;
for ( key in params ) {
if ( !hasOwn.call( params, key ) ) {
continue;
}
querystring += encodeURIComponent( key ) + "=" +
encodeURIComponent( params[ key ] ) + "&";
}
return window.location.pathname + querystring.slice( 0, -1 );
},
extend: extend,
id: id,
addEvent: addEvent
});
//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
//Doing this allows us to tell if the following methods have been overwritten on the actual
//QUnit object, which is a deprecated way of using the callbacks.
extend(QUnit.constructor.prototype, {
// Logging callbacks; all receive a single argument with the listed properties
// run test/logs.html for any related changes
begin: registerLoggingCallback('begin'),
// done: { failed, passed, total, runtime }
done: registerLoggingCallback('done'),
// log: { result, actual, expected, message }
log: registerLoggingCallback('log'),
// testStart: { name }
testStart: registerLoggingCallback('testStart'),
// testDone: { name, failed, passed, total }
testDone: registerLoggingCallback('testDone'),
// moduleStart: { name }
moduleStart: registerLoggingCallback('moduleStart'),
// moduleDone: { name, failed, passed, total }
moduleDone: registerLoggingCallback('moduleDone')
});
if ( typeof document === "undefined" || document.readyState === "complete" ) {
config.autorun = true;
}
QUnit.load = function() {
runLoggingCallbacks( 'begin', QUnit, {} );
// Initialize the config, saving the execution queue
var oldconfig = extend({}, config);
QUnit.init();
extend(config, oldconfig);
config.blocking = false;
var urlConfigHtml = '', len = config.urlConfig.length;
for ( var i = 0, val; i < len; i++ ) {
val = config.urlConfig[i];
config[val] = QUnit.urlParams[val];
urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>';
}
var userAgent = id("qunit-userAgent");
if ( userAgent ) {
userAgent.innerHTML = navigator.userAgent;
}
var banner = id("qunit-header");
if ( banner ) {
banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
}
var toolbar = id("qunit-testrunner-toolbar");
if ( toolbar ) {
var filter = document.createElement("input");
filter.type = "checkbox";
filter.id = "qunit-filter-pass";
addEvent( filter, "click", function() {
var ol = document.getElementById("qunit-tests");
if ( filter.checked ) {
ol.className = ol.className + " hidepass";
} else {
var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
ol.className = tmp.replace(/ hidepass /, " ");
}
if ( defined.sessionStorage ) {
if (filter.checked) {
sessionStorage.setItem("qunit-filter-passed-tests", "true");
} else {
sessionStorage.removeItem("qunit-filter-passed-tests");
}
}
});
if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
filter.checked = true;
var ol = document.getElementById("qunit-tests");
ol.className = ol.className + " hidepass";
}
toolbar.appendChild( filter );
var label = document.createElement("label");
label.setAttribute("for", "qunit-filter-pass");
label.innerHTML = "Hide passed tests";
toolbar.appendChild( label );
}
var main = id('qunit-fixture');
if ( main ) {
config.fixture = main.innerHTML;
}
if (config.autostart) {
QUnit.start();
}
};
addEvent(window, "load", QUnit.load);
// addEvent(window, "error") gives us a useless event object
window.onerror = function( message, file, line ) {
if ( QUnit.config.current ) {
QUnit.pushFailure( message, file + ":" + line );
} else {
QUnit.test( "global failure", function() {
QUnit.pushFailure( message, file + ":" + line );
});
}
};
function done() {
config.autorun = true;
// Log the last module results
if ( config.currentModule ) {
runLoggingCallbacks( 'moduleDone', QUnit, {
name: config.currentModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
} );
}
var banner = id("qunit-banner"),
tests = id("qunit-tests"),
runtime = +new Date() - config.started,
passed = config.stats.all - config.stats.bad,
html = [
'Tests completed in ',
runtime,
' milliseconds.<br/>',
'<span class="passed">',
passed,
'</span> tests of <span class="total">',
config.stats.all,
'</span> passed, <span class="failed">',
config.stats.bad,
'</span> failed.'
].join('');
if ( banner ) {
banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
}
if ( tests ) {
id( "qunit-testresult" ).innerHTML = html;
}
if ( config.altertitle && typeof document !== "undefined" && document.title ) {
// show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
document.title = [
(config.stats.bad ? "\u2716" : "\u2714"),
document.title.replace(/^[\u2714\u2716] /i, "")
].join(" ");
}
// clear own sessionStorage items if all tests passed
if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
var key;
for ( var i = 0; i < sessionStorage.length; i++ ) {
key = sessionStorage.key( i++ );
if ( key.indexOf("qunit-test-") === 0 ) {
sessionStorage.removeItem( key );
}
}
}
runLoggingCallbacks( 'done', QUnit, {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
} );
}
function validTest( name ) {
var filter = config.filter,
run = false;
if ( !filter ) {
return true;
}
var not = filter.charAt( 0 ) === "!";
if ( not ) {
filter = filter.slice( 1 );
}
if ( name.indexOf( filter ) !== -1 ) {
return !not;
}
if ( not ) {
run = true;
}
return run;
}
// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
// Later Safari and IE10 are supposed to support error.stack as well
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
offset = offset || 3;
if (e.stacktrace) {
// Opera
return e.stacktrace.split("\n")[offset + 3];
} else if (e.stack) {
// Firefox, Chrome
var stack = e.stack.split("\n");
if (/^error$/i.test(stack[0])) {
stack.shift();
}
return stack[offset];
} else if (e.sourceURL) {
// Safari, PhantomJS
// hopefully one day Safari provides actual stacktraces
// exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) {
return;
}
// for actual exceptions, this is useful
return e.sourceURL + ":" + e.line;
}
}
function sourceFromStacktrace(offset) {
try {
throw new Error();
} catch ( e ) {
return extractStacktrace( e, offset );
}
}
function escapeInnerText(s) {
if (!s) {
return "";
}
s = s + "";
return s.replace(/[\&<>]/g, function(s) {
switch(s) {
case "&": return "&amp;";
case "<": return "&lt;";
case ">": return "&gt;";
default: return s;
}
});
}
function synchronize( callback, last ) {
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
process(last);
}
}
function process( last ) {
function next() {
process( last );
}
var start = new Date().getTime();
config.depth = config.depth ? config.depth + 1 : 1;
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
config.queue.shift()();
} else {
window.setTimeout( next, 13 );
break;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}
function saveGlobal() {
config.pollution = [];
if ( config.noglobals ) {
for ( var key in window ) {
if ( !hasOwn.call( window, key ) ) {
continue;
}
config.pollution.push( key );
}
}
}
function checkPollution( name ) {
var old = config.pollution;
saveGlobal();
var newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
}
var deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
}
}
// returns a new Array with the elements that are in a but not in b
function diff( a, b ) {
var result = a.slice();
for ( var i = 0; i < result.length; i++ ) {
for ( var j = 0; j < b.length; j++ ) {
if ( result[i] === b[j] ) {
result.splice(i, 1);
i--;
break;
}
}
}
return result;
}
function extend(a, b) {
for ( var prop in b ) {
if ( b[prop] === undefined ) {
delete a[prop];
// Avoid "Member not found" error in IE8 caused by setting window.constructor
} else if ( prop !== "constructor" || a !== window ) {
a[prop] = b[prop];
}
}
return a;
}
function addEvent(elem, type, fn) {
if ( elem.addEventListener ) {
elem.addEventListener( type, fn, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, fn );
} else {
fn();
}
}
function id(name) {
return !!(typeof document !== "undefined" && document && document.getElementById) &&
document.getElementById( name );
}
function registerLoggingCallback(key){
return function(callback){
config[key].push( callback );
};
}
// Supports deprecated method of completely overwriting logging callbacks
function runLoggingCallbacks(key, scope, args) {
//debugger;
var callbacks;
if ( QUnit.hasOwnProperty(key) ) {
QUnit[key].call(scope, args);
} else {
callbacks = config[key];
for( var i = 0; i < callbacks.length; i++ ) {
callbacks[i].call( scope, args );
}
}
}
// Test for equality any JavaScript type.
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = (function() {
var innerEquiv; // the real equiv function
var callers = []; // stack to decide between skip/abort functions
var parents = []; // stack to avoiding loops from circular referencing
// Call the o related callback with the given arguments.
function bindCallbacks(o, callbacks, args) {
var prop = QUnit.objectType(o);
if (prop) {
if (QUnit.objectType(callbacks[prop]) === "function") {
return callbacks[prop].apply(callbacks, args);
} else {
return callbacks[prop]; // or undefined
}
}
}
var getProto = Object.getPrototypeOf || function (obj) {
return obj.__proto__;
};
var callbacks = (function () {
// for string, boolean, number and null
function useStrictEquality(b, a) {
if (b instanceof a.constructor || a instanceof b.constructor) {
// to catch short annotaion VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
}
return {
"string" : useStrictEquality,
"boolean" : useStrictEquality,
"number" : useStrictEquality,
"null" : useStrictEquality,
"undefined" : useStrictEquality,
"nan" : function(b) {
return isNaN(b);
},
"date" : function(b, a) {
return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
},
"regexp" : function(b, a) {
return QUnit.objectType(b) === "regexp" &&
// the regex itself
a.source === b.source &&
// and its modifers
a.global === b.global &&
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function" : function() {
var caller = callers[callers.length - 1];
return caller !== Object && typeof caller !== "undefined";
},
"array" : function(b, a) {
var i, j, loop;
var len;
// b could be an object literal here
if (QUnit.objectType(b) !== "array") {
return false;
}
len = a.length;
if (len !== b.length) { // safe and faster
return false;
}
// track reference to avoid circular references
parents.push(a);
for (i = 0; i < len; i++) {
loop = false;
for (j = 0; j < parents.length; j++) {
if (parents[j] === a[i]) {
loop = true;// dont rewalk array
}
}
if (!loop && !innerEquiv(a[i], b[i])) {
parents.pop();
return false;
}
}
parents.pop();
return true;
},
"object" : function(b, a) {
var i, j, loop;
var eq = true; // unless we can proove it
var aProperties = [], bProperties = []; // collection of
// strings
// comparing constructors is more strict than using
// instanceof
if (a.constructor !== b.constructor) {
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if (!((getProto(a) === null && getProto(b) === Object.prototype) ||
(getProto(b) === null && getProto(a) === Object.prototype)))
{
return false;
}
}
// stack constructor before traversing properties
callers.push(a.constructor);
// track reference to avoid circular references
parents.push(a);
for (i in a) { // be strict: don't ensures hasOwnProperty
// and go deep
loop = false;
for (j = 0; j < parents.length; j++) {
if (parents[j] === a[i]) {
// don't go down the same path twice
loop = true;
}
}
aProperties.push(i); // collect a's properties
if (!loop && !innerEquiv(a[i], b[i])) {
eq = false;
break;
}
}
callers.pop(); // unstack, we are done
parents.pop();
for (i in b) {
bProperties.push(i); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv(aProperties.sort(), bProperties.sort());
}
};
}());
innerEquiv = function() { // can take multiple arguments
var args = Array.prototype.slice.apply(arguments);
if (args.length < 2) {
return true; // end transition
}
return (function(a, b) {
if (a === b) {
return true; // catch the most you can
} else if (a === null || b === null || typeof a === "undefined" ||
typeof b === "undefined" ||
QUnit.objectType(a) !== QUnit.objectType(b)) {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [ b, a ]);
}
// apply transition with (1..n) arguments
}(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)));
};
return innerEquiv;
}());
/**
* jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
* http://flesler.blogspot.com Licensed under BSD
* (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
*
* @projectDescription Advanced and extensible data dumping for Javascript.
* @version 1.0.0
* @author Ariel Flesler
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
*/
QUnit.jsDump = (function() {
function quote( str ) {
return '"' + str.toString().replace(/"/g, '\\"') + '"';
}
function literal( o ) {
return o + '';
}
function join( pre, arr, post ) {
var s = jsDump.separator(),
base = jsDump.indent(),
inner = jsDump.indent(1);
if ( arr.join ) {
arr = arr.join( ',' + s + inner );
}
if ( !arr ) {
return pre + post;
}
return [ pre, inner + arr, base + post ].join(s);
}
function array( arr, stack ) {
var i = arr.length, ret = new Array(i);
this.up();
while ( i-- ) {
ret[i] = this.parse( arr[i] , undefined , stack);
}
this.down();
return join( '[', ret, ']' );
}
var reName = /^function (\w+)/;
var jsDump = {
parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
stack = stack || [ ];
var parser = this.parsers[ type || this.typeOf(obj) ];
type = typeof parser;
var inStack = inArray(obj, stack);
if (inStack != -1) {
return 'recursion('+(inStack - stack.length)+')';
}
//else
if (type == 'function') {
stack.push(obj);
var res = parser.call( this, obj, stack );
stack.pop();
return res;
}
// else
return (type == 'string') ? parser : this.parsers.error;
},
typeOf: function( obj ) {
var type;
if ( obj === null ) {
type = "null";
} else if (typeof obj === "undefined") {
type = "undefined";
} else if (QUnit.is("RegExp", obj)) {
type = "regexp";
} else if (QUnit.is("Date", obj)) {
type = "date";
} else if (QUnit.is("Function", obj)) {
type = "function";
} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
type = "window";
} else if (obj.nodeType === 9) {
type = "document";
} else if (obj.nodeType) {
type = "node";
} else if (
// native arrays
toString.call( obj ) === "[object Array]" ||
// NodeList objects
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
) {
type = "array";
} else {
type = typeof obj;
}
return type;
},
separator: function() {
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
},
indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
if ( !this.multiline ) {
return '';
}
var chr = this.indentChar;
if ( this.HTML ) {
chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
}
return new Array( this._depth_ + (extra||0) ).join(chr);
},
up: function( a ) {
this._depth_ += a || 1;
},
down: function( a ) {
this._depth_ -= a || 1;
},
setParser: function( name, parser ) {
this.parsers[name] = parser;
},
// The next 3 are exposed so you can use them
quote: quote,
literal: literal,
join: join,
//
_depth_: 1,
// This is the list of parsers, to modify them, use jsDump.setParser
parsers: {
window: '[Window]',
document: '[Document]',
error: '[ERROR]', //when no parser is found, shouldn't happen
unknown: '[Unknown]',
'null': 'null',
'undefined': 'undefined',
'function': function( fn ) {
var ret = 'function',
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
if ( name ) {
ret += ' ' + name;
}
ret += '(';
ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
},
array: array,
nodelist: array,
'arguments': array,
object: function( map, stack ) {
var ret = [ ], keys, key, val, i;
QUnit.jsDump.up();
if (Object.keys) {
keys = Object.keys( map );
} else {
keys = [];
for (key in map) { keys.push( key ); }
}
keys.sort();
for (i = 0; i < keys.length; i++) {
key = keys[ i ];
val = map[ key ];
ret.push( QUnit.jsDump.parse( key, 'key' ) + ': ' + QUnit.jsDump.parse( val, undefined, stack ) );
}
QUnit.jsDump.down();
return join( '{', ret, '}' );
},
node: function( node ) {
var open = QUnit.jsDump.HTML ? '&lt;' : '<',
close = QUnit.jsDump.HTML ? '&gt;' : '>';
var tag = node.nodeName.toLowerCase(),
ret = open + tag;
for ( var a in QUnit.jsDump.DOMAttrs ) {
var val = node[QUnit.jsDump.DOMAttrs[a]];
if ( val ) {
ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
}
}
return ret + close + open + '/' + tag + close;
},
functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
var l = fn.length;
if ( !l ) {
return '';
}
var args = new Array(l);
while ( l-- ) {
args[l] = String.fromCharCode(97+l);//97 is 'a'
}
return ' ' + args.join(', ') + ' ';
},
key: quote, //object calls it internally, the key part of an item in a map
functionCode: '[code]', //function calls it internally, it's the content of the function
attribute: quote, //node calls it internally, it's an html attribute value
string: quote,
date: quote,
regexp: literal, //regex
number: literal,
'boolean': literal
},
DOMAttrs:{//attributes to dump from nodes, name=>realName
id:'id',
name:'name',
'class':'className'
},
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
indentChar:' ',//indentation unit
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
};
return jsDump;
}());
// from Sizzle.js
function getText( elems ) {
var ret = "", elem;
for ( var i = 0; elems[i]; i++ ) {
elem = elems[i];
// Get the text from text nodes and CDATA nodes
if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
ret += elem.nodeValue;
// Traverse everything else, except comment nodes
} else if ( elem.nodeType !== 8 ) {
ret += getText( elem.childNodes );
}
}
return ret;
}
//from jquery.js
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
}
/*
* Javascript Diff Algorithm
* By John Resig (http://ejohn.org/)
* Modified by Chu Alan "sprite"
*
* Released under the MIT license.
*
* More Info:
* http://ejohn.org/projects/javascript-diff-algorithm/
*
* Usage: QUnit.diff(expected, actual)
*
* QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
*/
QUnit.diff = (function() {
function diff(o, n) {
var ns = {};
var os = {};
var i;
for (i = 0; i < n.length; i++) {
if (ns[n[i]] == null) {
ns[n[i]] = {
rows: [],
o: null
};
}
ns[n[i]].rows.push(i);
}
for (i = 0; i < o.length; i++) {
if (os[o[i]] == null) {
os[o[i]] = {
rows: [],
n: null
};
}
os[o[i]].rows.push(i);
}
for (i in ns) {
if ( !hasOwn.call( ns, i ) ) {
continue;
}
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
n[ns[i].rows[0]] = {
text: n[ns[i].rows[0]],
row: os[i].rows[0]
};
o[os[i].rows[0]] = {
text: o[os[i].rows[0]],
row: ns[i].rows[0]
};
}
}
for (i = 0; i < n.length - 1; i++) {
if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
n[i + 1] == o[n[i].row + 1]) {
n[i + 1] = {
text: n[i + 1],
row: n[i].row + 1
};
o[n[i].row + 1] = {
text: o[n[i].row + 1],
row: i + 1
};
}
}
for (i = n.length - 1; i > 0; i--) {
if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
n[i - 1] == o[n[i].row - 1]) {
n[i - 1] = {
text: n[i - 1],
row: n[i].row - 1
};
o[n[i].row - 1] = {
text: o[n[i].row - 1],
row: i - 1
};
}
}
return {
o: o,
n: n
};
}
return function(o, n) {
o = o.replace(/\s+$/, '');
n = n.replace(/\s+$/, '');
var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/));
var str = "";
var i;
var oSpace = o.match(/\s+/g);
if (oSpace == null) {
oSpace = [" "];
}
else {
oSpace.push(" ");
}
var nSpace = n.match(/\s+/g);
if (nSpace == null) {
nSpace = [" "];
}
else {
nSpace.push(" ");
}
if (out.n.length === 0) {
for (i = 0; i < out.o.length; i++) {
str += '<del>' + out.o[i] + oSpace[i] + "</del>";
}
}
else {
if (out.n[0].text == null) {
for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
str += '<del>' + out.o[n] + oSpace[n] + "</del>";
}
}
for (i = 0; i < out.n.length; i++) {
if (out.n[i].text == null) {
str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
}
else {
var pre = "";
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
}
str += " " + out.n[i].text + nSpace[i] + pre;
}
}
}
return str;
};
}());
// for CommonJS enviroments, export everything
if ( typeof exports !== "undefined" || typeof require !== "undefined" ) {
extend(exports, QUnit);
}
// get at whatever the global object is, like window in browsers
}( (function() {return this;}.call()) ));
/*
RequireJS 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(Z){function x(b){return J.call(b)==="[object Function]"}function E(b){return J.call(b)==="[object Array]"}function o(b,e){if(b){var f;for(f=0;f<b.length;f+=1)if(b[f]&&e(b[f],f,b))break}}function M(b,e){if(b){var f;for(f=b.length-1;f>-1;f-=1)if(b[f]&&e(b[f],f,b))break}}function y(b,e){for(var f in b)if(b.hasOwnProperty(f)&&e(b[f],f))break}function N(b,e,f,h){e&&y(e,function(e,j){if(f||!F.call(b,j))h&&typeof e!=="string"?(b[j]||(b[j]={}),N(b[j],e,f,h)):b[j]=e});return b}function t(b,e){return function(){return e.apply(b,
arguments)}}function $(b){if(!b)return b;var e=Z;o(b.split("."),function(b){e=e[b]});return e}function aa(b,e,f){return function(){var h=ga.call(arguments,0),c;if(f&&x(c=h[h.length-1]))c.__requireJsBuild=!0;h.push(e);return b.apply(null,h)}}function ba(b,e,f){o([["toUrl"],["undef"],["defined","requireDefined"],["specified","requireSpecified"]],function(h){var c=h[1]||h[0];b[h[0]]=e?aa(e[c],f):function(){var b=z[O];return b[c].apply(b,arguments)}})}function G(b,e,f,h){e=Error(e+"\nhttp://requirejs.org/docs/errors.html#"+
b);e.requireType=b;e.requireModules=h;if(f)e.originalError=f;return e}function ha(){if(H&&H.readyState==="interactive")return H;M(document.getElementsByTagName("script"),function(b){if(b.readyState==="interactive")return H=b});return H}var j,p,u,B,s,C,H,I,ca,da,ia=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ja=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/\.js$/,ka=/^\.\//;p=Object.prototype;var J=p.toString,F=p.hasOwnProperty;p=Array.prototype;var ga=p.slice,la=p.splice,w=!!(typeof window!==
"undefined"&&navigator&&document),fa=!w&&typeof importScripts!=="undefined",ma=w&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,O="_",S=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",z={},r={},P=[],K=!1;if(typeof define==="undefined"){if(typeof requirejs!=="undefined"){if(x(requirejs))return;r=requirejs;requirejs=void 0}typeof require!=="undefined"&&!x(require)&&(r=require,require=void 0);j=requirejs=function(b,e,f,h){var c,o=O;!E(b)&&typeof b!=="string"&&
(c=b,E(e)?(b=e,e=f,f=h):b=[]);if(c&&c.context)o=c.context;(h=z[o])||(h=z[o]=j.s.newContext(o));c&&h.configure(c);return h.require(b,e,f)};j.config=function(b){return j(b)};require||(require=j);j.version="2.0.6";j.jsExtRegExp=/^\/|:|\?|\.js$/;j.isBrowser=w;p=j.s={contexts:z,newContext:function(b){function e(a,d,k){var l,b,i,v,e,c,f,g=d&&d.split("/");l=g;var h=m.map,j=h&&h["*"];if(a&&a.charAt(0)===".")if(d){l=m.pkgs[d]?g=[d]:g.slice(0,g.length-1);d=a=l.concat(a.split("/"));for(l=0;d[l];l+=1)if(b=d[l],
b===".")d.splice(l,1),l-=1;else if(b==="..")if(l===1&&(d[2]===".."||d[0]===".."))break;else l>0&&(d.splice(l-1,2),l-=2);l=m.pkgs[d=a[0]];a=a.join("/");l&&a===d+"/"+l.main&&(a=d)}else a.indexOf("./")===0&&(a=a.substring(2));if(k&&(g||j)&&h){d=a.split("/");for(l=d.length;l>0;l-=1){i=d.slice(0,l).join("/");if(g)for(b=g.length;b>0;b-=1)if(k=h[g.slice(0,b).join("/")])if(k=k[i]){v=k;e=l;break}if(v)break;!c&&j&&j[i]&&(c=j[i],f=l)}!v&&c&&(v=c,e=f);v&&(d.splice(0,e,v),a=d.join("/"))}return a}function f(a){w&&
o(document.getElementsByTagName("script"),function(d){if(d.getAttribute("data-requiremodule")===a&&d.getAttribute("data-requirecontext")===g.contextName)return d.parentNode.removeChild(d),!0})}function h(a){var d=m.paths[a];if(d&&E(d)&&d.length>1)return f(a),d.shift(),g.undef(a),g.require([a]),!0}function c(a,d,k,l){var b,i,v=a?a.indexOf("!"):-1,c=null,f=d?d.name:null,h=a,j=!0,m="";a||(j=!1,a="_@r"+(M+=1));v!==-1&&(c=a.substring(0,v),a=a.substring(v+1,a.length));c&&(c=e(c,f,l),i=q[c]);a&&(c?m=i&&
i.normalize?i.normalize(a,function(a){return e(a,f,l)}):e(a,f,l):(m=e(a,f,l),b=g.nameToUrl(m)));a=c&&!i&&!k?"_unnormalized"+(O+=1):"";return{prefix:c,name:m,parentMap:d,unnormalized:!!a,url:b,originalName:h,isDefine:j,id:(c?c+"!"+m:m)+a}}function p(a){var d=a.id,k=n[d];k||(k=n[d]=new g.Module(a));return k}function r(a,d,k){var b=a.id,c=n[b];if(F.call(q,b)&&(!c||c.defineEmitComplete))d==="defined"&&k(q[b]);else p(a).on(d,k)}function A(a,d){var k=a.requireModules,b=!1;if(d)d(a);else if(o(k,function(d){if(d=
n[d])d.error=a,d.events.error&&(b=!0,d.emit("error",a))}),!b)j.onError(a)}function s(){P.length&&(la.apply(D,[D.length-1,0].concat(P)),P=[])}function u(a,d,k){a=a&&a.map;d=aa(k||g.require,a,d);ba(d,g,a);d.isBrowser=w;return d}function z(a){delete n[a];o(L,function(d,k){if(d.map.id===a)return L.splice(k,1),d.defined||(g.waitCount-=1),!0})}function B(a,d,k){var b=a.map.id,c=a.depMaps,i;if(a.inited){if(d[b])return a;d[b]=!0;o(c,function(a){var a=a.id,b=n[a];return!b||k[a]||!b.inited||!b.enabled?void 0:
i=B(b,d,k)});k[b]=!0;return i}}function C(a,d,b){var l=a.map.id,c=a.depMaps;if(a.inited&&a.map.isDefine){if(d[l])return q[l];d[l]=a;o(c,function(i){var i=i.id,c=n[i];!Q[i]&&c&&(!c.inited||!c.enabled?b[l]=!0:(c=C(c,d,b),b[i]||a.defineDepById(i,c)))});a.check(!0);return q[l]}}function I(a){a.check()}function T(){var a,d,b,l,c=(b=m.waitSeconds*1E3)&&g.startTime+b<(new Date).getTime(),i=[],e=!1,j=!0;if(!U){U=!0;y(n,function(b){a=b.map;d=a.id;if(b.enabled&&!b.error)if(!b.inited&&c)h(d)?e=l=!0:(i.push(d),
f(d));else if(!b.inited&&b.fetched&&a.isDefine&&(e=!0,!a.prefix))return j=!1});if(c&&i.length)return b=G("timeout","Load timeout for modules: "+i,null,i),b.contextName=g.contextName,A(b);j&&(o(L,function(a){if(!a.defined){var a=B(a,{},{}),d={};a&&(C(a,d,{}),y(d,I))}}),y(n,I));if((!c||l)&&e)if((w||fa)&&!V)V=setTimeout(function(){V=0;T()},50);U=!1}}function W(a){p(c(a[0],null,!0)).init(a[1],a[2])}function J(a){var a=a.currentTarget||a.srcElement,d=g.onScriptLoad;a.detachEvent&&!S?a.detachEvent("onreadystatechange",
d):a.removeEventListener("load",d,!1);d=g.onScriptError;a.detachEvent&&!S||a.removeEventListener("error",d,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}var U,X,g,Q,V,m={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{}},n={},Y={},D=[],q={},R={},M=1,O=1,L=[];Q={require:function(a){return u(a)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports=q[a.map.id]={}},module:function(a){return a.module={id:a.map.id,uri:a.map.url,config:function(){return m.config&&m.config[a.map.id]||
{}},exports:q[a.map.id]}}};X=function(a){this.events=Y[a.id]||{};this.map=a;this.shim=m.shim[a.id];this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};X.prototype={init:function(a,d,b,c){c=c||{};if(!this.inited){this.factory=d;if(b)this.on("error",b);else this.events.error&&(b=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.depMaps.rjsSkipMap=a.rjsSkipMap;this.errback=b;this.inited=!0;this.ignore=c.ignore;c.enabled||this.enabled?this.enable():
this.check()}},defineDepById:function(a,d){var b;o(this.depMaps,function(d,c){if(d.id===a)return b=c,!0});return this.defineDep(b,d)},defineDep:function(a,d){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=d)},fetch:function(){if(!this.fetched){this.fetched=!0;g.startTime=(new Date).getTime();var a=this.map;if(this.shim)u(this,!0)(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},
load:function(){var a=this.map.url;R[a]||(R[a]=!0,g.load(this.map.id,a))},check:function(a){if(this.enabled&&!this.enabling){var d,b,c=this.map.id;b=this.depExports;var e=this.exports,i=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(this.depCount<1&&!this.defined){if(x(i)){if(this.events.error)try{e=g.execCb(c,i,b,e)}catch(f){d=f}else e=g.execCb(c,i,b,e);if(this.map.isDefine)if((b=this.module)&&b.exports!==void 0&&b.exports!==this.exports)e=
b.exports;else if(e===void 0&&this.usingExports)e=this.exports;if(d)return d.requireMap=this.map,d.requireModules=[this.map.id],d.requireType="define",A(this.error=d)}else e=i;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,j.onResourceLoad))j.onResourceLoad(g,this.map,this.depMaps);delete n[c];this.defined=!0;g.waitCount-=1;g.waitCount===0&&(L=[])}this.defining=!1;if(!a&&this.defined&&!this.defineEmitted)this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0}}else this.fetch()}},
callPlugin:function(){var a=this.map,d=a.id,b=c(a.prefix,null,!1,!0);r(b,"defined",t(this,function(b){var k;k=this.map.name;var i=this.map.parentMap?this.map.parentMap.name:null;if(this.map.unnormalized){if(b.normalize&&(k=b.normalize(k,function(a){return e(a,i,!0)})||""),b=c(a.prefix+"!"+k,this.map.parentMap,!1,!0),r(b,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),b=n[b.id]){if(this.events.error)b.on("error",t(this,function(a){this.emit("error",a)}));
b.enable()}}else k=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),k.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=[d];y(n,function(a){a.map.id.indexOf(d+"_unnormalized")===0&&z(a.map.id)});A(a)}),k.fromText=function(a,b){var d=K;d&&(K=!1);p(c(a));j.exec(b);d&&(K=!0);g.completeLoad(a)},b.load(a.name,u(a.parentMap,!0,function(a,b,d){a.rjsSkipMap=!0;return g.require(a,b,d)}),k,m)}));g.enable(b,this);this.pluginMaps[b.id]=b},enable:function(){this.enabled=
!0;if(!this.waitPushed)L.push(this),g.waitCount+=1,this.waitPushed=!0;this.enabling=!0;o(this.depMaps,t(this,function(a,b){var k,e;if(typeof a==="string"){a=c(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.depMaps.rjsSkipMap);this.depMaps[b]=a;if(k=Q[a.id]){this.depExports[b]=k(this);return}this.depCount+=1;r(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&r(a,"error",this.errback)}k=a.id;e=n[k];!Q[k]&&e&&!e.enabled&&g.enable(a,this)}));y(this.pluginMaps,
t(this,function(a){var b=n[a.id];b&&!b.enabled&&g.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){o(this.events[a],function(a){a(b)});a==="error"&&delete this.events[a]}};return g={config:m,contextName:b,registry:n,defined:q,urlFetched:R,waitCount:0,defQueue:D,Module:X,makeModuleMap:c,configure:function(a){a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");var b=m.pkgs,e=m.shim,f=m.paths,
j=m.map;N(m,a,!0);m.paths=N(f,a.paths,!0);if(a.map)m.map=N(j||{},a.map,!0,!0);if(a.shim)y(a.shim,function(a,b){E(a)&&(a={deps:a});if(a.exports&&!a.exports.__buildReady)a.exports=g.makeShimExports(a.exports);e[b]=a}),m.shim=e;if(a.packages)o(a.packages,function(a){a=typeof a==="string"?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ka,"").replace(ea,"")}}),m.pkgs=b;y(n,function(a,b){if(!a.inited&&!a.map.unnormalized)a.map=c(b)});if(a.deps||a.callback)g.require(a.deps||
[],a.callback)},makeShimExports:function(a){var b;return typeof a==="string"?(b=function(){return $(a)},b.exports=a,b):function(){return a.apply(Z,arguments)}},requireDefined:function(a,b){var e=c(a,b,!1,!0).id;return F.call(q,e)},requireSpecified:function(a,b){a=c(a,b,!1,!0).id;return F.call(q,a)||F.call(n,a)},require:function(a,d,e,f){var h;if(typeof a==="string"){if(x(d))return A(G("requireargs","Invalid require call"),e);if(j.get)return j.get(g,a,d);a=c(a,d,!1,!0);a=a.id;return!F.call(q,a)?A(G("notloaded",
'Module name "'+a+'" has not been loaded yet for context: '+b)):q[a]}e&&!x(e)&&(f=e,e=void 0);d&&!x(d)&&(f=d,d=void 0);for(s();D.length;)if(h=D.shift(),h[0]===null)return A(G("mismatch","Mismatched anonymous define() module: "+h[h.length-1]));else W(h);p(c(null,f)).init(a,d,e,{enabled:!0});T();return g.require},undef:function(a){s();var b=c(a,null,!0),e=n[a];delete q[a];delete R[b.url];delete Y[a];if(e){if(e.events.defined)Y[a]=e.events;z(a)}},enable:function(a){n[a.id]&&p(a).enable()},completeLoad:function(a){var b,
c,e=m.shim[a]||{},f=e.exports&&e.exports.exports;for(s();D.length;){c=D.shift();if(c[0]===null){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);W(c)}c=n[a];if(!b&&!q[a]&&c&&!c.inited)if(m.enforceDefine&&(!f||!$(f)))if(h(a))return;else return A(G("nodefine","No define call for "+a,null,[a]));else W([a,e.deps||[],e.exports]);T()},toUrl:function(a,b){var c=a.lastIndexOf("."),f=null;c!==-1&&(f=a.substring(c,a.length),a=a.substring(0,c));return g.nameToUrl(e(a,b&&b.id,!0),f)},nameToUrl:function(a,b){var c,
e,f,i,h,g;if(j.jsExtRegExp.test(a))i=a+(b||"");else{c=m.paths;e=m.pkgs;i=a.split("/");for(h=i.length;h>0;h-=1)if(g=i.slice(0,h).join("/"),f=e[g],g=c[g]){E(g)&&(g=g[0]);i.splice(0,h,g);break}else if(f){c=a===f.name?f.location+"/"+f.main:f.location;i.splice(0,h,c);break}i=i.join("/");i+=b||(/\?/.test(i)?"":".js");i=(i.charAt(0)==="/"||i.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+i}return m.urlArgs?i+((i.indexOf("?")===-1?"?":"&")+m.urlArgs):i},load:function(a,b){j.load(g,a,b)},execCb:function(a,b,c,e){return b.apply(e,
c)},onScriptLoad:function(a){if(a.type==="load"||ma.test((a.currentTarget||a.srcElement).readyState))H=null,a=J(a),g.completeLoad(a.id)},onScriptError:function(a){var b=J(a);if(!h(b.id))return A(G("scripterror","Script error",a,[b.id]))}}}};j({});ba(j);if(w&&(u=p.head=document.getElementsByTagName("head")[0],B=document.getElementsByTagName("base")[0]))u=p.head=B.parentNode;j.onError=function(b){throw b;};j.load=function(b,e,f){var h=b&&b.config||{},c;if(w)return c=h.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml",
"html:script"):document.createElement("script"),c.type=h.scriptType||"text/javascript",c.charset="utf-8",c.async=!0,c.setAttribute("data-requirecontext",b.contextName),c.setAttribute("data-requiremodule",e),c.attachEvent&&!(c.attachEvent.toString&&c.attachEvent.toString().indexOf("[native code")<0)&&!S?(K=!0,c.attachEvent("onreadystatechange",b.onScriptLoad)):(c.addEventListener("load",b.onScriptLoad,!1),c.addEventListener("error",b.onScriptError,!1)),c.src=f,I=c,B?u.insertBefore(c,B):u.appendChild(c),
I=null,c;else fa&&(importScripts(f),b.completeLoad(e))};w&&M(document.getElementsByTagName("script"),function(b){if(!u)u=b.parentNode;if(s=b.getAttribute("data-main")){if(!r.baseUrl)C=s.split("/"),ca=C.pop(),da=C.length?C.join("/")+"/":"./",r.baseUrl=da,s=ca;s=s.replace(ea,"");r.deps=r.deps?r.deps.concat(s):[s];return!0}});define=function(b,e,f){var h,c;typeof b!=="string"&&(f=e,e=b,b=null);E(e)||(f=e,e=[]);!e.length&&x(f)&&f.length&&(f.toString().replace(ia,"").replace(ja,function(b,c){e.push(c)}),
e=(f.length===1?["require"]:["require","exports","module"]).concat(e));if(K&&(h=I||ha()))b||(b=h.getAttribute("data-requiremodule")),c=z[h.getAttribute("data-requirecontext")];(c?c.defQueue:P).push([b,e,f])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(r)}})(this);
/**
* sinon-qunit 1.0.0, 2010/12/09
*
* @author Christian Johansen (christian@cjohansen.no)
*
* (The BSD License)
*
* Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, QUnit, test*/
sinon.assert.fail = function (msg) {
QUnit.ok(false, msg);
};
sinon.assert.pass = function (assertion) {
QUnit.ok(true, assertion);
};
sinon.config = {
injectIntoThis: true,
injectInto: null,
properties: ["spy", "stub", "mock", "clock", "sandbox"],
useFakeTimers: true,
useFakeServer: false
};
(function (global) {
var qTest = QUnit.test;
QUnit.test = global.test = function (testName, expected, callback, async) {
if (arguments.length === 2) {
callback = expected;
expected = null;
}
return qTest(testName, expected, sinon.test(callback), async);
};
}(this));
This source diff could not be displayed because it is too large. You can view the blob instead.
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
if(typeof module!="undefined"&&module.exports)module.exports=sjcl;
sjcl.cipher.aes=function(a){this.h[0][0][0]||this.w();var b,c,d,e,f=this.h[0][4],g=this.h[1];b=a.length;var h=1;if(b!==4&&b!==6&&b!==8)throw new sjcl.exception.invalid("invalid aes key size");this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(a%b===0||b===8&&a%b===4){c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255];if(a%b===0){c=c<<8^c>>>24^h<<24;h=h<<1^(h>>7)*283}}d[a]=d[a-b]^c}for(b=0;a;b++,a--){c=d[b&3?a:a-4];e[b]=a<=4||b<4?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^
g[3][f[c&255]]}};
sjcl.cipher.aes.prototype={encrypt:function(a){return this.H(a,0)},decrypt:function(a){return this.H(a,1)},h:[[[],[],[],[],[]],[[],[],[],[],[]]],w:function(){var a=this.h[0],b=this.h[1],c=a[4],d=b[4],e,f,g,h=[],i=[],k,j,l,m;for(e=0;e<0x100;e++)i[(h[e]=e<<1^(e>>7)*283)^e]=e;for(f=g=0;!c[f];f^=k||1,g=i[g]||1){l=g^g<<1^g<<2^g<<3^g<<4;l=l>>8^l&255^99;c[f]=l;d[l]=f;j=h[e=h[k=h[f]]];m=j*0x1010101^e*0x10001^k*0x101^f*0x1010100;j=h[l]*0x101^l*0x1010100;for(e=0;e<4;e++){a[e][f]=j=j<<24^j>>>8;b[e][l]=m=m<<24^m>>>8}}for(e=
0;e<5;e++){a[e]=a[e].slice(0);b[e]=b[e].slice(0)}},H:function(a,b){if(a.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var c=this.a[b],d=a[0]^c[0],e=a[b?3:1]^c[1],f=a[2]^c[2];a=a[b?1:3]^c[3];var g,h,i,k=c.length/4-2,j,l=4,m=[0,0,0,0];g=this.h[b];var n=g[0],o=g[1],p=g[2],q=g[3],r=g[4];for(j=0;j<k;j++){g=n[d>>>24]^o[e>>16&255]^p[f>>8&255]^q[a&255]^c[l];h=n[e>>>24]^o[f>>16&255]^p[a>>8&255]^q[d&255]^c[l+1];i=n[f>>>24]^o[a>>16&255]^p[d>>8&255]^q[e&255]^c[l+2];a=n[a>>>24]^o[d>>16&
255]^p[e>>8&255]^q[f&255]^c[l+3];l+=4;d=g;e=h;f=i}for(j=0;j<4;j++){m[b?3&-j:j]=r[d>>>24]<<24^r[e>>16&255]<<16^r[f>>8&255]<<8^r[a&255]^c[l++];g=d;d=e;e=f;f=a;a=g}return m}};
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===undefined?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(a.length===0||b.length===0)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return d===32?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;
if(b===0)return 0;return(b-1)*32+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(a.length*32<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;if(c>0&&b)a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1);return a},partial:function(a,b,c){if(a===32)return b;return(c?b|0:b<<32-a)+a*0x10000000000},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return false;var c=0,d;for(d=0;d<a.length;d++)c|=
a[d]^b[d];return c===0},P:function(a,b,c,d){var e;e=0;if(d===undefined)d=[];for(;b>=32;b-=32){d.push(c);c=0}if(b===0)return d.concat(a);for(e=0;e<a.length;e++){d.push(c|a[e]>>>b);c=a[e]<<32-b}e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,b+a>32?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++){if((d&3)===0)e=a[d/4];b+=String.fromCharCode(e>>>24);e<<=8}return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++){d=d<<8|a.charCodeAt(c);if((c&3)===3){b.push(d);d=0}}c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,d*4)}};
sjcl.codec.base64={D:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.D,g=0,h=sjcl.bitArray.bitLength(a);if(c)f=f.substr(0,62)+"-_";for(c=0;d.length*6<h;){d+=f.charAt((g^a[c]>>>e)>>>26);if(e<6){g=a[c]<<6-e;e+=26;c++}else{g<<=6;e-=6}}for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d=0,e=sjcl.codec.base64.D,f=0,g;if(b)e=e.substr(0,62)+"-_";for(b=0;b<a.length;b++){g=e.indexOf(a.charAt(b));
if(g<0)throw new sjcl.exception.invalid("this isn't base64!");if(d>26){d-=26;c.push(f^g>>>d);f=g<<32-d}else{d+=6;f^=g<<32-d}}d&56&&c.push(sjcl.bitArray.partial(d&56,f,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.w();if(a){this.n=a.n.slice(0);this.i=a.i.slice(0);this.e=a.e}else this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.n=this.N.slice(0);this.i=[];this.e=0;return this},update:function(a){if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);var b,c=this.i=sjcl.bitArray.concat(this.i,a);b=this.e;a=this.e=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.C(c.splice(0,16));return this},finalize:function(){var a,b=this.i,c=this.n;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.e/
4294967296));for(b.push(this.e|0);b.length;)this.C(b.splice(0,16));this.reset();return c},N:[],a:[],w:function(){function a(e){return(e-Math.floor(e))*0x100000000|0}var b=0,c=2,d;a:for(;b<64;c++){for(d=2;d*d<=c;d++)if(c%d===0)continue a;if(b<8)this.N[b]=a(Math.pow(c,0.5));this.a[b]=a(Math.pow(c,1/3));b++}},C:function(a){var b,c,d=a.slice(0),e=this.n,f=this.a,g=e[0],h=e[1],i=e[2],k=e[3],j=e[4],l=e[5],m=e[6],n=e[7];for(a=0;a<64;a++){if(a<16)b=d[a];else{b=d[a+1&15];c=d[a+14&15];b=d[a&15]=(b>>>7^b>>>18^
b>>>3^b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+d[a&15]+d[a+9&15]|0}b=b+n+(j>>>6^j>>>11^j>>>25^j<<26^j<<21^j<<7)+(m^j&(l^m))+f[a];n=m;m=l;l=j;j=k+b|0;k=i;i=h;h=g;g=b+(h&i^k&(h^i))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0}e[0]=e[0]+g|0;e[1]=e[1]+h|0;e[2]=e[2]+i|0;e[3]=e[3]+k|0;e[4]=e[4]+j|0;e[5]=e[5]+l|0;e[6]=e[6]+m|0;e[7]=e[7]+n|0}};
sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,i=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];if(i<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;f<4&&k>>>8*f;f++);if(f<15-i)f=15-i;c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.G(a,b,c,d,e,f);g=sjcl.mode.ccm.I(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),i=f.clamp(b,h-e),k=f.bitSlice(b,
h-e);h=(h-e)/8;if(g<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;b<4&&h>>>8*b;b++);if(b<15-g)b=15-g;c=f.clamp(c,8*(15-b));i=sjcl.mode.ccm.I(a,i,c,k,e,b);a=sjcl.mode.ccm.G(a,i.data,c,d,e,b);if(!f.equal(i.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return i.data},G:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,i=h.k;e/=8;if(e%2||e<4||e>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(d.length>0xffffffff||b.length>0xffffffff)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;if(c<=65279)g=[h.partial(16,c)];else if(c<=0xffffffff)g=h.concat([h.partial(16,65534)],[c]);g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(i(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(i(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,e*8)},I:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.k;var i=b.length,k=h.bitLength(b);c=h.concat([h.partial(8,
f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!i)return{tag:d,data:[]};for(g=0;g<i;g+=4){c[3]++;e=a.encrypt(c);b[g]^=e[0];b[g+1]^=e[1];b[g+2]^=e[2];b[g+3]^=e[3]}return{tag:d,data:h.clamp(b,k)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.A,i=sjcl.bitArray,k=i.k,j=[0,0,0,0];c=h(a.encrypt(c));var l,m=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4){l=b.slice(g,g+4);j=k(j,l);m=m.concat(k(c,a.encrypt(k(c,l))));c=h(c)}l=b.slice(g);b=i.bitLength(l);g=a.encrypt(k(c,[0,0,0,b]));l=i.clamp(k(l.concat([0,0,0]),g),b);j=k(j,k(l.concat([0,0,0]),g));j=a.encrypt(k(j,k(c,h(c))));
if(d.length)j=k(j,f?d:sjcl.mode.ocb2.pmac(a,d));return m.concat(i.concat(l,i.clamp(j,e)))},decrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.A,h=sjcl.bitArray,i=h.k,k=[0,0,0,0],j=g(a.encrypt(c)),l,m,n=sjcl.bitArray.bitLength(b)-e,o=[];d=d||[];for(c=0;c+4<n/32;c+=4){l=i(j,a.decrypt(i(j,b.slice(c,c+4))));k=i(k,l);o=o.concat(l);j=g(j)}m=n-c*32;l=a.encrypt(i(j,[0,0,0,m]));l=i(l,h.clamp(b.slice(c),
m).concat([0,0,0]));k=i(k,l);k=a.encrypt(i(k,i(j,g(j))));if(d.length)k=i(k,f?d:sjcl.mode.ocb2.pmac(a,d));if(!h.equal(h.clamp(k,e),h.bitSlice(b,n)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return o.concat(h.clamp(l,m))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.A,e=sjcl.bitArray,f=e.k,g=[0,0,0,0],h=a.encrypt([0,0,0,0]);h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4){h=d(h);g=f(g,a.encrypt(f(h,b.slice(c,c+4))))}b=b.slice(c);if(e.bitLength(b)<128){h=f(h,d(h));b=e.concat(b,[2147483648|0,0,
0,0])}g=f(g,b);return a.encrypt(f(d(f(h,d(h))),g))},A:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^(a[0]>>>31)*135]}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d=b.prototype.blockSize/32;this.l=[new b,new b];if(a.length>d)a=b.hash(a);for(b=0;b<d;b++){c[0][b]=a[b]^909522486;c[1][b]=a[b]^1549556828}this.l[0].update(c[0]);this.l[1].update(c[1])};
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a,b){a=(new this.M(this.l[0])).update(a,b).finalize();return(new this.M(this.l[1])).update(a).finalize()};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;if(d<0||c<0)throw sjcl.exception.invalid("invalid params to pbkdf2");if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,i,k=[],j=sjcl.bitArray;for(i=1;32*k.length<(d||1);i++){e=f=a.encrypt(j.concat(b,[i]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}if(d)k=j.clamp(k,d);return k};
sjcl.random={randomWords:function(a,b){var c=[];b=this.isReady(b);var d;if(b===0)throw new sjcl.exception.notReady("generator isn't seeded");else b&2&&this.U(!(b&1));for(b=0;b<a;b+=4){(b+1)%0x10000===0&&this.L();d=this.u();c.push(d[0],d[1],d[2],d[3])}this.L();return c.slice(0,a)},setDefaultParanoia:function(a){this.t=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.q[c],h=this.isReady();d=this.F[c];if(d===undefined)d=this.F[c]=this.R++;if(g===undefined)g=this.q[c]=0;this.q[c]=
(this.q[c]+1)%this.b.length;switch(typeof a){case "number":break;case "object":if(b===undefined)for(c=b=0;c<a.length;c++)for(e=a[c];e>0;){b++;e>>>=1}this.b[g].update([d,this.J++,2,b,f,a.length].concat(a));break;case "string":if(b===undefined)b=a.length;this.b[g].update([d,this.J++,3,b,f,a.length]);this.b[g].update(a);break;default:throw new sjcl.exception.bug("random: addEntropy only supports number, array or string");}this.j[g]+=b;this.f+=b;if(h===0){this.isReady()!==0&&this.K("seeded",Math.max(this.g,
this.f));this.K("progress",this.getProgress())}},isReady:function(a){a=this.B[a!==undefined?a:this.t];return this.g&&this.g>=a?this.j[0]>80&&(new Date).valueOf()>this.O?3:1:this.f>=a?2:0},getProgress:function(a){a=this.B[a?a:this.t];return this.g>=a?1["0"]:this.f>a?1["0"]:this.f/a},startCollectors:function(){if(!this.m){if(window.addEventListener){window.addEventListener("load",this.o,false);window.addEventListener("mousemove",this.p,false)}else if(document.attachEvent){document.attachEvent("onload",
this.o);document.attachEvent("onmousemove",this.p)}else throw new sjcl.exception.bug("can't attach event");this.m=true}},stopCollectors:function(){if(this.m){if(window.removeEventListener){window.removeEventListener("load",this.o,false);window.removeEventListener("mousemove",this.p,false)}else if(window.detachEvent){window.detachEvent("onload",this.o);window.detachEvent("onmousemove",this.p)}this.m=false}},addEventListener:function(a,b){this.r[a][this.Q++]=b},removeEventListener:function(a,b){var c;
a=this.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&a[c]===b&&d.push(c);for(b=0;b<d.length;b++){c=d[b];delete a[c]}},b:[new sjcl.hash.sha256],j:[0],z:0,q:{},J:0,F:{},R:0,g:0,f:0,O:0,a:[0,0,0,0,0,0,0,0],d:[0,0,0,0],s:undefined,t:6,m:false,r:{progress:{},seeded:{}},Q:0,B:[0,48,64,96,128,192,0x100,384,512,768,1024],u:function(){for(var a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}return this.s.encrypt(this.d)},L:function(){this.a=this.u().concat(this.u());this.s=new sjcl.cipher.aes(this.a)},
T:function(a){this.a=sjcl.hash.sha256.hash(this.a.concat(a));this.s=new sjcl.cipher.aes(this.a);for(a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}},U:function(a){var b=[],c=0,d;this.O=b[0]=(new Date).valueOf()+3E4;for(d=0;d<16;d++)b.push(Math.random()*0x100000000|0);for(d=0;d<this.b.length;d++){b=b.concat(this.b[d].finalize());c+=this.j[d];this.j[d]=0;if(!a&&this.z&1<<d)break}if(this.z>=1<<this.b.length){this.b.push(new sjcl.hash.sha256);this.j.push(0)}this.f-=c;if(c>this.g)this.g=c;this.z++;
this.T(b)},p:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX,a.y||a.clientY||a.offsetY],2,"mouse")},o:function(){sjcl.random.addEntropy(new Date,2,"loadtime")},K:function(a,b){var c;a=sjcl.random.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&d.push(a[c]);for(c=0;c<d.length;c++)d[c](b)}};try{var s=new Uint32Array(32);crypto.getRandomValues(s);sjcl.random.addEntropy(s,1024,"crypto['getRandomValues']")}catch(t){}
sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.c({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.c(f,c);c=f.adata;if(typeof f.salt==="string")f.salt=sjcl.codec.base64.toBits(f.salt);if(typeof f.iv==="string")f.iv=sjcl.codec.base64.toBits(f.iv);if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||typeof a==="string"&&f.iter<=100||f.ts!==64&&f.ts!==96&&f.ts!==128||f.ks!==128&&f.ks!==192&&f.ks!==0x100||f.iv.length<
2||f.iv.length>4)throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof a==="string"){g=sjcl.misc.cachedPbkdf2(a,f);a=g.key.slice(0,f.ks/32);f.salt=g.salt}if(typeof b==="string")b=sjcl.codec.utf8String.toBits(b);if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);g=new sjcl.cipher[f.cipher](a);e.c(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(e.V(f,e.defaults))},decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.c(e.c(e.c({},e.defaults),
e.decode(b)),c,true);var f;c=b.adata;if(typeof b.salt==="string")b.salt=sjcl.codec.base64.toBits(b.salt);if(typeof b.iv==="string")b.iv=sjcl.codec.base64.toBits(b.iv);if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||typeof a==="string"&&b.iter<=100||b.ts!==64&&b.ts!==96&&b.ts!==128||b.ks!==128&&b.ks!==192&&b.ks!==0x100||!b.iv||b.iv.length<2||b.iv.length>4)throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof a==="string"){f=sjcl.misc.cachedPbkdf2(a,b);a=f.key.slice(0,b.ks/32);
b.salt=f.salt}if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.c(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';
break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],1)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");
b[d[2]]=d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4])}return b},c:function(a,b,c){if(a===undefined)a={};if(b===undefined)return a;var d;for(d in b)if(b.hasOwnProperty(d)){if(c&&a[d]!==undefined&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},V:function(a,b){var c={},d;for(d in a)if(a.hasOwnProperty(d)&&a[d]!==b[d])c[d]=a[d];return c},W:function(a,b){var c={},d;for(d=0;d<b.length;d++)if(a[b[d]]!==
undefined)c[b[d]]=a[b[d]];return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.S={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.S,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===undefined?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
// Adds 3 dummy storages to JIO
// type:
// - dummyallok
// - dummyallfail
// - dummyallnotfound
// - dummyall3tries
(function () { var jioDummyStorageLoader = function ( jIO ) {
////////////////////////////////////////////////////////////////////////////
// Dummy Storage 1 : all ok
var newDummyStorageAllOk = function ( spec, my ) {
var that = my.basicStorage( spec, my );
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.username = spec.username;
return o;
};
that.post = function (command) {
setTimeout (function () {
that.success ({
ok:true,
id:command.getDocId()
});
}, 100);
}; // end post
that.put = function (command) {
setTimeout (function () {
that.success ({
ok:true,
id:command.getDocId()
});
}, 100); // 100 ms, for jiotests simple job waiting
}; // end put
that.get = function (command) {
setTimeout(function () {
that.success ({
_id:command.getDocId(),
content:'content',
_creation_date: 10000,
_last_modified: 15000
});
}, 100);
}; // end get
that.allDocs = function (command) {
setTimeout(function () {
var o = {
total_rows: 2,
rows: [{
id:'file',
key:'file',
value: {
content:'filecontent',
_creation_date:10000,
_last_modified:15000
}
},{
id:'memo',
key:'memo',
value: {
content:'memocontent',
_creation_date:20000,
_last_modified:25000
}
}]
};
if (command.getOption('metadata_only')) {
delete o.rows[0].value.content;
delete o.rows[1].value.content;
}
that.success (o);
}, 100);
}; // end allDocs
that.remove = function (command) {
setTimeout (function () {
that.success ({ok:true,id:command.getDocId()});
}, 100);
}; // end remove
return that;
},
// end Dummy Storage All Ok
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Dummy Storage 2 : all fail
newDummyStorageAllFail = function ( spec, my ) {
var that = my.basicStorage( spec, my ), priv = {};
priv.error = function () {
setTimeout (function () {
that.error ({status:0,statusText:'Unknown Error',
error:'unknown_error',
message:'Execution encountred an error.',
reason:'Execution encountred an error'});
});
};
that.post = function (command) {
priv.error();
}; // end post
that.put = function (command) {
priv.error();
}; // end put
that.get = function (command) {
priv.error();
}; // end get
that.allDocs = function (command) {
priv.error();
}; // end allDocs
that.remove = function (command) {
priv.error();
}; // end remove
return that;
},
// end Dummy Storage All Fail
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Dummy Storage 3 : all not found
newDummyStorageAllNotFound = function ( spec, my ) {
var that = my.basicStorage( spec, my );
that.post = function (command) {
setTimeout (function () {
that.success ({
ok:true,
id:command.getDocId()
});
}, 100);
}; // end post
that.put = function (command) {
setTimeout (function () {
that.success ({
ok:true,
id:command.getDocId()
});
}, 100);
}; // end put
that.get = function (command) {
setTimeout(function () {
that.error ({status:404,statusText:'Not Found',
error:'not_found',
message:'Document "'+ command.getDocId() +
'" not found.',
reason:'Document "'+ command.getDocId() +
'" not found'});
}, 100);
}; // end get
that.allDocs = function (command) {
setTimeout(function () {
that.error ({status:404,statusText:'Not Found',
error:'not_found',
message:'User list not found.',
reason:'User list not found'});
}, 100);
}; // end allDocs
that.remove = function (command) {
setTimeout (function () {
that.success ({
ok:true,
id:command.getDocId()
});
}, 100);
}; // end remove
return that;
},
// end Dummy Storage All Not Found
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Dummy Storage 4 : all 3 tries
newDummyStorageAll3Tries = function ( spec, my ) {
var that = my.basicStorage( spec, my ), priv = {};
// this serialized method is used to make simple difference between
// two dummyall3tries storages:
// so {type:'dummyall3tries',a:'b'} differs from
// {type:'dummyall3tries',c:'d'}.
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.applicationname = spec.applicationname;
return o;
};
priv.doJob = function (command,if_ok_return) {
// wait a little in order to simulate asynchronous operation
setTimeout(function () {
priv.Try3OKElseFail (command.getTried(),if_ok_return);
}, 100);
};
priv.Try3OKElseFail = function (tries,if_ok_return) {
if ( typeof tries === 'undefined' ) {
return that.error ({status:0,statusText:'Unknown Error',
error:'unknown_error',
message:'Cannot get tried.',
reason:'Cannot get tried'});
}
if ( tries < 3 ) {
return that.retry (
{message:'' + (3 - tries) + ' tries left.'});
}
if ( tries === 3 ) {
return that.success (if_ok_return);
}
if ( tries > 3 ) {
return that.error ({status:1,statusText:'Too Much Tries',
error:'too_much_tries',
message:'Too much tries.',
reason:'Too much tries'});
}
};
that.post = function (command) {
priv.doJob (command,{ok:true,id:command.getDocId()});
}; // end post
that.put = function (command) {
priv.doJob (command,{ok:true,id:command.getDocId()});
}; // end put
that.get = function (command) {
priv.doJob (command,{
_id: command.getDocId(),
content: 'content '+command.getDocId(),
_creation_date: 11000,
_last_modified: 17000
});
}; // end get
that.allDocs = function (command) {
priv.doJob(command,{
total_rows:2,
rows:[{
id:'file',key:'file',
value:{
_creation_date:10000,
_last_modified:15000
}
},{
id:'memo',key:'memo',
value:{
_creation_date:20000,
_last_modified:25000
}
}]});
}; // end allDocs
that.remove = function (command) {
priv.doJob(command,{ok:true,id:command.getDocId()});
}; // end remove
return that;
};
// end Dummy Storage All 3 Tries
////////////////////////////////////////////////////////////////////////////
// add key to storageObjectType of global jio
jIO.addStorageType('dummyallok', newDummyStorageAllOk);
jIO.addStorageType('dummyallfail', newDummyStorageAllFail);
jIO.addStorageType('dummyallnotfound', newDummyStorageAllNotFound);
jIO.addStorageType('dummyall3tries', newDummyStorageAll3Tries);
};
if (window.requirejs) {
define ('JIODummyStorages',['jIO'], jioDummyStorageLoader);
} else {
jioDummyStorageLoader ( jIO );
}
}());
auto: grunt
check-syntax: grunt
grunt:
make -C ../../grunt/*_gruntJIOStorage > /dev/null 2>&1
var newConflictManagerStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
var storage_exists = (spec.storage?true:false);
priv.secondstorage_spec = spec.storage || {type:'base'};
priv.secondstorage_string = JSON.stringify (priv.secondstorage_spec);
var local_namespace = 'jio/conflictmanager/'+
priv.secondstorage_string+'/';
var empty_fun = function (){};
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.storage = priv.secondstorage_spec;
return o;
};
that.validateState = function () {
if (storage_exists) {
return '';
}
return 'Need at least one parameter: "storage".';
};
priv.getDistantMetadata = function (command,path,success,error) {
var cloned_option = command.cloneOption ();
cloned_option.metadata_only = false;
that.addJob ('get',priv.secondstorage_spec,path,cloned_option,
success, error);
};
priv.saveMetadataToDistant = function (command,path,content,success,error) {
that.addJob ('put',priv.secondstorage_spec,
{_id:path,content:JSON.stringify (content)},
command.cloneOption(),success,error);
};
priv.saveNewRevision = function (command,path,content,success,error) {
that.addJob ('post',priv.secondstorage_spec,{_id:path,content:content},
command.cloneOption(),success,error);
};
priv.loadRevision = function (command,path,success,error) {
that.addJob('get',priv.secondstorage_spec,path,command.cloneOption(),
success, error);
};
priv.deleteAFile = function (command,path,success,error) {
var cloned_option = command.cloneOption();
that.addJob ('remove',priv.secondstorage_spec,{_id:path},
command.cloneOption(), success, error);
};
priv.chooseARevision = function (metadata) {
var tmp_last_modified = 0, ret_rev = '', rev;
for (rev in metadata) {
if (tmp_last_modified <
metadata[rev]._last_modified) {
tmp_last_modified =
metadata[rev]._last_modified;
ret_rev = rev;
}
}
return ret_rev;
};
priv._revs = function (metadata,revision) {
if (!(metadata && revision)) { return null; }
if (metadata[revision]) {
return {start:metadata[revision]._revisions.length,
ids:metadata[revision]._revisions};
} else {
return null;
}
};
priv._revs_info = function (metadata) {
if (!metadata) { return null; }
var k, l = [];
for (k in metadata) {
l.push({
rev:k,status:(metadata[k]?(
metadata[k]._deleted?'deleted':'available'):'missing')
});
}
return l;
};
priv.solveConflict = function (doc,option,param) {
var o = {}, am = priv.newAsyncModule(),
command = param.command,
metadata_file_path = param.docid + '.metadata',
current_revision = '',
current_revision_file_path = '',
metadata_file_content = null,
on_conflict = false, conflict_object = {total_rows:0,rows:[]},
on_remove = param._deleted,
previous_revision = param.previous_revision,
previous_revision_content_object = null,
now = new Date(),
failerror;
o.getDistantMetadata = function (){
priv.getDistantMetadata (
command, metadata_file_path,
function (result) {
var previous_revision_number =
parseInt(previous_revision.split('-')[0],10);
metadata_file_content = JSON.parse (result.content);
// set current revision
current_revision = (previous_revision_number + 1) + '-' +
hex_sha256 ('' + doc.content +
previous_revision +
JSON.stringify (metadata_file_content));
current_revision_file_path = param.docid + '.' +
current_revision;
previous_revision_content_object = metadata_file_content[
previous_revision] || {};
if (!on_remove) {
am.wait(o,'saveMetadataOnDistant',1);
am.call(o,'saveNewRevision');
}
am.call(o,'previousUpdateMetadata');
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.saveNewRevision = function (){
priv.saveNewRevision (
command, current_revision_file_path, doc.content,
function (result) {
am.call(o,'saveMetadataOnDistant');
}, function (error) {
am.call(o,'error',[error]);
}
);
};
o.previousUpdateMetadata = function () {
var i;
for (i = 0; i < param.key.length; i+= 1) {
delete metadata_file_content[param.key[i]];
}
am.call(o,'checkForConflicts');
};
o.checkForConflicts = function () {
var rev;
for (rev in metadata_file_content) {
var revision_index;
on_conflict = true;
failerror = {
status:409,error:'conflict',
statusText:'Conflict',reason:'document update conflict',
message:'There is one or more conflicts'
};
break;
}
am.call(o,'updateMetadata');
};
o.updateMetadata = function (){
var revision_history, id = '';
id = current_revision.split('-'); id.shift(); id = id.join('-');
revision_history = previous_revision_content_object._revisions;
revision_history.unshift(id);
metadata_file_content[current_revision] = {
_creation_date:previous_revision_content_object._creation_date||
now.getTime(),
_last_modified: now.getTime(),
_revisions: revision_history,
_conflict: on_conflict,
_deleted: on_remove
};
if (on_conflict) {
conflict_object =
priv.createConflictObject(
command, metadata_file_content, current_revision
);
}
am.call(o,'saveMetadataOnDistant');
};
o.saveMetadataOnDistant = function (){
priv.saveMetadataToDistant(
command, metadata_file_path, metadata_file_content,
function (result) {
am.call(o,'deleteAllConflictingRevision');
if (on_conflict) {
am.call(o,'error');
} else {
am.call(o,'success');
}
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.deleteAllConflictingRevision = function (){
var i;
for (i = 0; i < param.key.length; i+= 1) {
priv.deleteAFile (
command, param.docid+'.'+param.key[i], empty_fun,empty_fun);
}
};
o.success = function (){
var a = {ok:true,id:param.docid,rev:current_revision};
am.neverCall(o,'error');
am.neverCall(o,'success');
if (option.revs) {
a.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (option.revs_info) {
a.revs_info = priv._revs_info(metadata_file_content);
}
if (option.conflicts) {
a.conflicts = conflict_object;
}
param.success(a);
};
o.error = function (error){
var err = error || failerror ||
{status:0,statusText:'Unknown',error:'unknown_error',
message:'Unknown error.',reason:'unknown error'};
if (current_revision) {
err.rev = current_revision;
}
if (option.revs) {
err.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (option.revs_info) {
err.revs_info = priv._revs_info(metadata_file_content);
}
if (option.conflicts) {
err.conflicts = conflict_object;
}
am.neverCall(o,'error');
am.neverCall(o,'success');
param.error(err);
};
am.call(o,'getDistantMetadata');
};
priv.createConflictObject = function (command, metadata, revision) {
return {
total_rows:1,
rows:[priv.createConflictRow(command,command.getDocId(),
metadata,revision)]
};
};
priv.getParam = function (list) {
var param = {}, i = 0;
if (typeof list[i] === 'string') {
param.content = list[i];
i ++;
}
if (typeof list[i] === 'object') {
param.options = list[i];
i ++;
} else {
param.options = {};
}
param.callback = function (err,val){};
param.success = function (val) {
param.callback(undefined,val);
};
param.error = function (err) {
param.callback(err,undefined);
};
if (typeof list[i] === 'function') {
if (typeof list[i+1] === 'function') {
param.success = list[i];
param.error = list[i+1];
} else {
param.callback = list[i];
}
}
return param;
};
priv.createConflictRow = function (command, docid, metadata, revision) {
var row = {id:docid,key:[],value:{
_solveConflict: function (/*content, option, success, error*/) {
var param = {}, got = priv.getParam(arguments);
if (got.content === undefined) {
param._deleted = true;
} else {
param._deleted = false;
}
param.success = got.success;
param.error = got.error;
param.previous_revision = revision;
param.docid = docid;
param.key = row.key;
param.command = command.clone();
return priv.solveConflict (
{_id:docid,content:got.content,_rev:revision},
got.options,param
);
}
}}, k;
for (k in metadata) {
row.key.push(k);
}
return row;
};
priv.newAsyncModule = function () {
var async = {};
async.call = function (obj,function_name,arglist) {
obj._wait = obj._wait || {};
if (obj._wait[function_name]) {
obj._wait[function_name]--;
return empty_fun;
}
// ok if undef or 0
arglist = arglist || [];
setTimeout(function(){
obj[function_name].apply(obj[function_name],arglist);
});
};
async.neverCall = function (obj,function_name) {
obj._wait = obj._wait || {};
obj._wait[function_name] = -1;
};
async.wait = function (obj,function_name,times) {
obj._wait = obj._wait || {};
obj._wait[function_name] = times;
};
async.end = function () {
async.call = empty_fun;
};
return async;
};
that.post = function (command) {
that.put (command);
};
/**
* Save a document and can manage conflicts.
* @method put
*/
that.put = function (command) {
var o = {}, am = priv.newAsyncModule(),
metadata_file_path = command.getDocId() + '.metadata',
current_revision = '',
current_revision_file_path = '',
metadata_file_content = null,
on_conflict = false, conflict_object = {total_rows:0,rows:[]},
previous_revision = command.getDocInfo('_rev') || '0',
previous_revision_file_path = command.getDocId() + '.' +
previous_revision,
now = new Date(),
failerror;
o.getDistantMetadata = function (){
priv.getDistantMetadata (
command,metadata_file_path,
function (result) {
var previous_revision_number =
parseInt(previous_revision.split('-')[0],10);
metadata_file_content = JSON.parse (result.content);
// set current revision
current_revision = (previous_revision_number + 1) + '-' +
hex_sha256 ('' + command.getDocContent() +
previous_revision +
JSON.stringify (metadata_file_content));
current_revision_file_path = command.getDocId() + '.' +
current_revision;
am.wait(o,'saveMetadataOnDistant',1);
am.call(o,'saveNewRevision');
am.call(o,'checkForConflicts');
},function (error) {
if (error.status === 404) {
current_revision = '1-' +
hex_sha256 (command.getDocContent());
current_revision_file_path = command.getDocId() + '.' +
current_revision;
am.wait(o,'saveMetadataOnDistant',1);
am.call(o,'saveNewRevision');
am.call(o,'createMetadata');
} else {
am.call(o,'error',[error]);
}
}
);
};
o.saveNewRevision = function (){
priv.saveNewRevision (
command,current_revision_file_path,command.getDocContent(),
function (result) {
am.call(o,'saveMetadataOnDistant');
}, function (error) {
am.call(o,'error',[error]);
}
);
};
o.checkForConflicts = function () {
var rev;
for (rev in metadata_file_content) {
if (rev !== previous_revision) {
on_conflict = true;
failerror = {
status:409,error:'conflict',
statusText:'Conflict',reason:'document update conflict',
message:'Document update conflict.'
};
break;
}
}
am.call(o,'updateMetadata');
};
o.createMetadata = function (){
var id = current_revision;
id = id.split('-'); id.shift(); id = id.join('-');
metadata_file_content = {};
metadata_file_content[current_revision] = {
_creation_date: now.getTime(),
_last_modified: now.getTime(),
_revisions: [id],
_conflict: false,
_deleted: false
};
am.call(o,'saveMetadataOnDistant');
};
o.updateMetadata = function (){
var previous_creation_date, revision_history = [], id = '';
if (metadata_file_content[previous_revision]) {
previous_creation_date = metadata_file_content[
previous_revision]._creation_date;
revision_history = metadata_file_content[
previous_revision]._revisions;
delete metadata_file_content[previous_revision];
}
id = current_revision.split('-'); id.shift(); id = id.join('-');
revision_history.unshift(id);
metadata_file_content[current_revision] = {
_creation_date: previous_creation_date || now.getTime(),
_last_modified: now.getTime(),
_revisions: revision_history,
_conflict: on_conflict,
_deleted: false
};
if (on_conflict) {
conflict_object =
priv.createConflictObject(
command, metadata_file_content, current_revision
);
}
am.call(o,'saveMetadataOnDistant');
};
o.saveMetadataOnDistant = function (){
priv.saveMetadataToDistant(
command, metadata_file_path, metadata_file_content,
function (result) {
am.call(o,'deletePreviousRevision');
if (on_conflict) {
am.call(o,'error');
} else {
am.call(o,'success');
}
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.deletePreviousRevision = function (){
if (previous_revision !== '0' /*&& !on_conflict*/) {
priv.deleteAFile (
command, previous_revision_file_path,
empty_fun,empty_fun);
}
};
o.success = function () {
var a = {ok:true,id:command.getDocId(),rev:current_revision};
am.neverCall(o,'error');
am.neverCall(o,'success');
if (command.getOption('revs')) {
a.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
a.revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
a.conflicts = conflict_object;
}
that.success(a);
};
o.error = function (error) {
var err = error || failerror ||
{status:0,statusText:'Unknown',error:'unknown_error',
message:'Unknown error.',reason:'unknown error'};
if (current_revision) {
err.rev = current_revision;
}
if (command.getOption('revs')) {
err.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
err.revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
err.conflicts = conflict_object;
}
am.neverCall(o,'error');
am.neverCall(o,'success');
that.error(err);
};
am.call(o,'getDistantMetadata');
}; // end put
/**
* Load a document from several storages, and send the first retreived
* document.
* @method get
*/
that.get = function (command) {
var o = {}, am = priv.newAsyncModule(),
metadata_file_path = command.getDocId() + '.metadata',
current_revision = command.getOption('rev') || '',
metadata_file_content = null,
metadata_only = command.getOption('metadata_only'),
on_conflict = false, conflict_object = {total_rows:0,rows:[]},
now = new Date(),
doc = {_id:command.getDocId()},
call404 = function (message) {
am.call(o,'error',[{
status:404,statusText:'Not Found',error:'not_found',
message:message,reason:message
}]);
};
o.getDistantMetadata = function (){
priv.getDistantMetadata (
command,metadata_file_path,
function (result) {
metadata_file_content = JSON.parse (result.content);
if (!metadata_only) {
am.wait(o,'success',1);
}
am.call(o,'affectMetadata');
am.call(o,'checkForConflicts');
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.affectMetadata = function () {
if (current_revision) {
if (!metadata_file_content[current_revision]) {
return call404('Document revision does not exists.');
}
} else {
current_revision = priv.chooseARevision(metadata_file_content);
}
doc._last_modified =
metadata_file_content[current_revision]._last_modified;
doc._creation_date =
metadata_file_content[current_revision]._creation_date;
doc._rev = current_revision;
if (metadata_only) {
am.call(o,'success');
} else {
am.call(o,'loadRevision');
}
};
o.loadRevision = function (){
if (!current_revision ||
metadata_file_content[current_revision]._deleted) {
return call404('Document has been removed.');
}
priv.loadRevision (
command, doc._id+'.'+current_revision,
function (result) {
doc.content = result.content;
am.call(o,'success');
}, function (error) {
am.call(o,'error',[error]);
}
);
};
o.checkForConflicts = function () {
if (metadata_file_content[current_revision]._conflict) {
on_conflict = true;
conflict_object =
priv.createConflictObject(
command,
metadata_file_content,
current_revision
);
}
am.call(o,'success');
};
o.success = function (){
am.neverCall(o,'error');
am.neverCall(o,'success');
if (command.getOption('revs')) {
doc._revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
doc._revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
doc._conflicts = conflict_object;
}
that.success(doc);
};
o.error = function (error) {
var err = error || {status:0,statusText:'Unknown',
message:'Unknown error.'};
if (command.getOption('revs')) {
err._revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
err._revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
err._conflicts = conflict_object;
}
am.neverCall(o,'error');
am.neverCall(o,'success');
that.error(err);
};
am.call(o,'getDistantMetadata');
};
/**
* Get a document list from several storages, and returns the first
* retreived document list.
* @method allDocs
*/
that.allDocs = function (command) {
var o = {}, am = priv.newAsyncModule(),
metadata_only = command.getOption('metadata_only'),
result_list = [], conflict_object = {total_rows:0,rows:[]},
nb_loaded_file = 0,
success_count = 0, success_max = 0;
o.retreiveList = function () {
var cloned_option = command.cloneOption (),
success = function (result) {
am.call(o,'filterTheList',[result]);
},error = function (error) {
am.call(o,'error',[error]);
};
cloned_option.metadata_only = true;
that.addJob ('allDocs',priv.secondstorage_spec,null,cloned_option,
success,error);
};
o.filterTheList = function (result) {
var i;
success_max ++;
for (i = 0; i < result.total_rows; i+= 1) {
var splitname = result.rows[i].id.split('.') || [];
if (splitname.length > 0 &&
splitname[splitname.length-1] === 'metadata') {
success_max ++;
splitname.length --;
am.call(o,'loadMetadataFile',[splitname.join('.')]);
}
}
am.call(o,'success');
};
o.loadMetadataFile = function (path) {
priv.getDistantMetadata (
command, path+'.metadata',
function (data) {
data = JSON.parse (data.content);
var revision = priv.chooseARevision(data);
if (!data[revision]._deleted) {
am.call(
o,'loadFile',[path,revision,data]
);
} else {
am.call(o,'success');
}
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.loadFile = function (path,revision,data) {
var doc = {
id: path,key: path,value:{
_last_modified:data[revision]._last_modified,
_creation_date:data[revision]._creation_date,
_rev:revision
}
};
if (command.getOption('revs')) {
doc.value._revisions = priv._revs(data,revision);
}
if (command.getOption('revs_info')) {
doc.value._revs_info = priv._revs_info(data,revision);
}
if (command.getOption('conflicts')) {
if (data[revision]._conflict) {
conflict_object.total_rows ++;
conflict_object.rows.push(priv.createConflictRow(
command, path, data, revision
));
}
}
if (!metadata_only) {
priv.loadRevision (
command,path+'.'+revision,
function (data) {
doc.content = data.content;
result_list.push(doc);
am.call(o,'success');
},function (error) {
am.call(o,'error',[error]);
});
} else {
result_list.push(doc);
am.call(o,'success');
}
};
o.success = function (){
var obj;
success_count ++;
if (success_count >= success_max) {
am.end();
obj = {total_rows:result_list.length,rows:result_list};
if (command.getOption('conflicts')) {
obj.conflicts = conflict_object;
}
that.success(obj);
}
};
o.error = function (error){
am.end();
that.error(error);
};
am.call(o,'retreiveList');
}; // end allDocs
/**
* Remove a document from several storages.
* @method remove
*/
that.remove = function (command) {
var o = {}, am = priv.newAsyncModule(),
metadata_file_path = command.getDocId() + '.metadata',
current_revision = '',
current_revision_file_path = '',
metadata_file_content = null,
on_conflict = false, conflict_object = {total_rows:0,rows:[]},
previous_revision = command.getOption('rev') || '0',
previous_revision_file_path = command.getDocId() + '.' +
previous_revision,
now = new Date(),
failerror;
o.getDistantMetadata = function (){
priv.getDistantMetadata (
command,metadata_file_path,
function (result) {
metadata_file_content = JSON.parse (result.content);
if (previous_revision === 'last') {
previous_revision =
priv.chooseARevision (metadata_file_content);
previous_revision_file_path = command.getDocId() + '.' +
previous_revision;
}
var previous_revision_number =
parseInt(previous_revision.split('-')[0],10) || 0;
// set current revision
current_revision = (previous_revision_number + 1) + '-' +
hex_sha256 ('' + previous_revision +
JSON.stringify (metadata_file_content));
current_revision_file_path = command.getDocId() + '.' +
current_revision;
am.call(o,'checkForConflicts');
},function (error) {
if (error.status === 404) {
am.call(o,'error',[{
status:404,statusText:'Not Found',
error:'not_found',reason:'missing',
message:'Document not found.'
}]);
} else {
am.call(o,'error',[error]);
}
}
);
};
o.checkForConflicts = function () {
var rev;
for (rev in metadata_file_content) {
if (rev !== previous_revision) {
on_conflict = true;
failerror = {
status:409,error:'conflict',
statusText:'Conflict',reason:'document update conflict',
message:'There is one or more conflicts'
};
break;
}
}
am.call(o,'updateMetadata');
};
o.updateMetadata = function (){
var previous_creation_date, revision_history = [], id = '';
if (metadata_file_content[previous_revision]) {
previous_creation_date = metadata_file_content[
previous_revision]._creation_date;
revision_history = metadata_file_content[
previous_revision]._revisions;
delete metadata_file_content[previous_revision];
}
id = current_revision;
id = id.split('-'); id.shift(); id = id.join('-');
revision_history.unshift(id);
metadata_file_content[current_revision] = {
_creation_date: previous_creation_date || now.getTime(),
_last_modified: now.getTime(),
_revisions: revision_history,
_conflict: on_conflict,
_deleted: true
};
if (on_conflict) {
conflict_object =
priv.createConflictObject(
command, metadata_file_content, current_revision
);
}
am.call(o,'saveMetadataOnDistant');
};
o.saveMetadataOnDistant = function (){
priv.saveMetadataToDistant(
command, metadata_file_path, metadata_file_content,
function (result) {
am.call(o,'deletePreviousRevision');
if (on_conflict) {
am.call(o,'error');
} else {
am.call(o,'success');
}
},function (error) {
am.call(o,'error',[error]);
}
);
};
o.deletePreviousRevision = function (){
if (previous_revision !== '0' /*&& !on_conflict*/) {
priv.deleteAFile (
command, previous_revision_file_path,
empty_fun,empty_fun);
}
};
o.success = function (revision){
var a = {ok:true,id:command.getDocId(),
rev:revision || current_revision};
am.neverCall(o,'error');
am.neverCall(o,'success');
if (command.getOption('revs')) {
a.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
a.revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
a.conflicts = conflict_object;
}
that.success(a);
};
o.error = function (error){
var err = error || failerror ||
{status:0,statusText:'Unknown',error:'unknown_error',
message:'Unknown error.',reason:'unknown error'};
if (current_revision) {
err.rev = current_revision;
}
if (command.getOption('revs')) {
err.revisions = priv._revs(metadata_file_content,
current_revision);
}
if (command.getOption('revs_info')) {
err.revs_info = priv._revs_info(metadata_file_content);
}
if (command.getOption('conflicts')) {
err.conflicts = conflict_object;
}
am.neverCall(o,'error');
am.neverCall(o,'success');
that.error(err);
};
am.call(o,'getDistantMetadata');
}; // end remove
return that;
};
jIO.addStorageType('conflictmanager', newConflictManagerStorage);
var newCryptedStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
var is_valid_storage = (spec.storage?true:false);
priv.username = spec.username || '';
priv.password = spec.password || '';
priv.secondstorage_spec = spec.storage || {type:'base'};
priv.secondstorage_string = JSON.stringify (priv.secondstorage_string);
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.username = priv.username;
o.password = priv.password; // TODO : unsecured !!!
o.storage = priv.secondstorage_string;
return o;
};
that.validateState = function () {
if (priv.username && is_valid_storage) {
return '';
}
return 'Need at least two parameters: "username" and "storage".';
};
// TODO : IT IS NOT SECURE AT ALL!
// WE MUST REWORK CRYPTED STORAGE!
priv.encrypt_param_object = {
"iv":"kaprWwY/Ucr7pumXoTHbpA",
"v":1,
"iter":1000,
"ks":256,
"ts":128,
"mode":"ccm",
"adata":"",
"cipher":"aes",
"salt":"K4bmZG9d704"
};
priv.decrypt_param_object = {
"iv":"kaprWwY/Ucr7pumXoTHbpA",
"ks":256,
"ts":128,
"salt":"K4bmZG9d704"
};
priv.encrypt = function (data,callback) {
// end with a callback in order to improve encrypt to an
// asynchronous encryption.
var tmp = sjcl.encrypt (priv.username+':'+
priv.password, data,
priv.encrypt_param_object);
callback(JSON.parse(tmp).ct);
};
priv.decrypt = function (data,callback) {
var tmp, param = $.extend(true,{},priv.decrypt_param_object);
param.ct = data || '';
param = JSON.stringify (param);
try {
tmp = sjcl.decrypt (priv.username+':'+
priv.password,
param);
} catch (e) {
callback({status:403,statusText:'Forbidden',error:'forbidden',
message:'Unable to decrypt.',reason:'unable to decrypt'});
return;
}
callback(undefined,tmp);
};
priv.newAsyncModule = function () {
var async = {};
async.call = function (obj,function_name,arglist) {
obj._wait = obj._wait || {};
if (obj._wait[function_name]) {
obj._wait[function_name]--;
return function () {};
}
// ok if undef or 0
arglist = arglist || [];
setTimeout(function (){
obj[function_name].apply(obj[function_name],arglist);
});
};
async.neverCall = function (obj,function_name) {
obj._wait = obj._wait || {};
obj._wait[function_name] = -1;
};
async.wait = function (obj,function_name,times) {
obj._wait = obj._wait || {};
obj._wait[function_name] = times;
};
async.end = function () {
async.call = function(){};
};
return async;
};
that.post = function (command) {
that.put (command);
};
/**
* Saves a document.
* @method put
*/
that.put = function (command) {
var new_file_name, new_file_content, am = priv.newAsyncModule(), o = {};
o.encryptFilePath = function () {
priv.encrypt(command.getDocId(),function(res) {
new_file_name = res;
am.call(o,'save');
});
};
o.encryptFileContent = function () {
priv.encrypt(command.getDocContent(),function(res) {
new_file_content = res;
am.call(o,'save');
});
};
o.save = function () {
var success = function (val) {
val.id = command.getDocId();
that.success (val);
},
error = function (err) {
that.error (err);
},
cloned_doc = command.cloneDoc();
cloned_doc._id = new_file_name;
cloned_doc.content = new_file_content;
that.addJob ('put',priv.secondstorage_spec,cloned_doc,
command.cloneOption(),success,error);
};
am.wait(o,'save',1);
am.call(o,'encryptFilePath');
am.call(o,'encryptFileContent');
}; // end put
/**
* Loads a document.
* @method get
*/
that.get = function (command) {
var new_file_name, option, am = priv.newAsyncModule(), o = {};
o.encryptFilePath = function () {
priv.encrypt(command.getDocId(),function(res) {
new_file_name = res;
am.call(o,'get');
});
};
o.get = function () {
that.addJob('get',priv.secondstorage_spec,new_file_name,
command.cloneOption(),o.success,o.error);
};
o.success = function (val) {
val._id = command.getDocId();
if (command.getOption('metadata_only')) {
that.success (val);
} else {
priv.decrypt (val.content, function(err,res){
if (err) {
that.error(err);
} else {
val.content = res;
that.success (val);
}
});
}
};
o.error = function (error) {
that.error(error);
};
am.call(o,'encryptFilePath');
}; // end get
/**
* Gets a document list.
* @method allDocs
*/
that.allDocs = function (command) {
var result_array = [], am = priv.newAsyncModule(), o = {};
o.allDocs = function () {
that.addJob ('allDocs', priv.secondstorage_spec, null,
command.cloneOption(), o.onSuccess, o.error);
};
o.onSuccess = function (val) {
if (val.total_rows === 0) {
return am.call(o,'success');
}
result_array = val.rows;
var i, decrypt = function (c) {
priv.decrypt (result_array[c].id,function (err,res) {
if (err) {
am.call(o,'error',[err]);
} else {
result_array[c].id = res;
result_array[c].key = res;
am.call(o,'success');
}
});
if (!command.getOption('metadata_only')) {
priv.decrypt (
result_array[c].value.content,
function (err,res) {
if (err) {
am.call(o,'error',[err]);
} else {
result_array[c].value.content = res;
am.call(o,'success');
}
});
}
};
if (command.getOption('metadata_only')) {
am.wait(o,'success',val.total_rows*1-1);
} else {
am.wait(o,'success',val.total_rows*2-1);
}
for (i = 0; i < result_array.length; i+= 1) {
decrypt(i);
}
};
o.error = function (error) {
am.end();
that.error (error);
};
o.success = function () {
am.end();
that.success ({total_rows:result_array.length,rows:result_array});
};
am.call(o,'allDocs');
}; // end allDocs
/**
* Removes a document.
* @method remove
*/
that.remove = function (command) {
var new_file_name, o = {};
o.encryptDocId = function () {
priv.encrypt(command.getDocId(),function(res) {
new_file_name = res;
o.removeDocument();
});
};
o.removeDocument = function () {
var cloned_doc = command.cloneDoc();
cloned_doc._id = new_file_name;
that.addJob ('remove', priv.secondstorage_spec, cloned_doc,
command.cloneOption(), o.success, that.error);
};
o.success = function (val) {
val.id = command.getDocId();
that.success (val);
};
o.encryptDocId();
}; // end remove
return that;
};
jIO.addStorageType('crypt', newCryptedStorage);
var newDAVStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
priv.secureDocId = function (string) {
var split = string.split('/'), i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i+= 1) {
if (split[i] === '') { return ''; }
}
return split.join('%2F');
};
priv.convertSlashes = function (string) {
return string.split('/').join('%2F');
};
priv.restoreSlashes = function (string) {
return string.split('%2F').join('/');
};
priv.username = spec.username || '';
priv.secured_username = priv.convertSlashes(priv.username);
priv.applicationname = spec.applicationname || 'untitled';
priv.secured_applicationname = priv.convertSlashes(priv.applicationname);
priv.url = spec.url || '';
priv.password = spec.password || ''; // TODO : is it secured ?
var super_serialized = that.serialized;
that.serialized = function() {
var o = super_serialized();
o.username = priv.username;
o.applicationname = priv.applicationname;
o.url = priv.url;
o.password = priv.password; // TODO : not realy secured...
return o;
};
/**
* If some other parameters is needed, it returns an error message.
* @method validateState
* @return {string} '' -> ok, 'message' -> error
*/
that.validateState = function() {
if (priv.secured_username && priv.url) {
return '';
}
return 'Need at least 2 parameters: "username" and "url".';
};
priv.newAsyncModule = function () {
var async = {};
async.call = function (obj,function_name,arglist) {
obj._wait = obj._wait || {};
if (obj._wait[function_name]) {
obj._wait[function_name]--;
return function () {};
}
// ok if undef or 0
arglist = arglist || [];
return obj[function_name].apply(obj[function_name],arglist);
};
async.neverCall = function (obj,function_name) {
obj._wait = obj._wait || {};
obj._wait[function_name] = -1;
};
async.wait = function (obj,function_name,times) {
obj._wait = obj._wait || {};
obj._wait[function_name] = times;
};
async.end = function () {
async.call = function(){};
};
return async;
};
priv.putOrPost = function (command,type) {
var secured_docid = priv.secureDocId(command.getDocId());
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' +
secured_docid + '?_=' + Date.now(), // to make url unique!
// and avoid chrome PUT on cache !
type: type,
data: command.getDocContent(),
async: true,
dataType: 'text', // TODO is it necessary ?
headers: {'Authorization':'Basic '+Base64.encode(
priv.username+':'+priv.password)},
// xhrFields: {withCredentials: 'true'}, // cross domain
success: function () {
that.success({ok:true,id:command.getDocId()});
},
error: function (type) {
// TODO : make statusText to lower case and add '_'
type.error = type.statusText;
type.reason = 'Cannot save "' + command.getDocId() + '"';
type.message = type.reason + '.';
that.retry(type);
}
} );
};
that.post = function (command) {
priv.putOrPost (command,'POST');
};
/**
* Saves a document in the distant dav storage.
* @method put
*/
that.put = function (command) {
priv.putOrPost (command,'PUT');
}; // end put
/**
* Loads a document from a distant dav storage.
* @method get
*/
that.get = function (command) {
var secured_docid = priv.secureDocId(command.getDocId()),
doc = {}, getContent = function () {
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' +
secured_docid + '?_=' + Date.now(),
type: "GET",
async: true,
dataType: 'text', // TODO is it necessary ?
headers: {'Authorization':'Basic '+Base64.encode(
priv.username + ':' + priv.password )},
// xhrFields: {withCredentials: 'true'}, // cross domain
success: function (content) {
doc.content = content;
that.success(doc);
},
error: function (type) {
type.error = type.statusText; // TODO : to lower case
if (type.status === 404) {
type.message = 'Document "' +
command.getDocId() +
'" not found.';
type.reason = 'missing';
that.error(type);
} else {
type.reason =
'An error occured when trying to get "' +
command.getDocId() + '"';
type.message = type.reason + '.';
that.retry(type);
}
}
} );
};
doc._id = command.getDocId();
// NOTE : if (command.getOption('content_only') { return getContent(); }
// Get properties
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' +
secured_docid + '?_=' + Date.now(),
type: "PROPFIND",
async: true,
dataType: 'xml',
headers: {'Authorization':'Basic '+Base64.encode(
priv.username + ':' + priv.password )},
success: function (xmlData) {
// doc.last_modified =
$(xmlData).find(
'lp1\\:getlastmodified, getlastmodified'
).each( function () {
doc._last_modified =
new Date($(this).text()).getTime();
});
$(xmlData).find(
'lp1\\:creationdate, creationdate'
).each( function () {
doc._creation_date =
new Date($(this).text()).getTime();
});
if (!command.getOption('metadata_only')) {
getContent();
} else {
that.success(doc);
}
},
error: function (type) {
if (type.status === 404) {
type.message = 'Cannot find "' + command.getDocId() +
'" informations.';
type.reason = 'missing';
that.error(type);
} else {
type.reason = 'Cannot get "' + command.getDocId() +
'" informations';
type.message = type.reason + '.';
that.retry(type);
}
}
} );
};
/**
* Gets a document list from a distant dav storage.
* @method allDocs
*/
that.allDocs = function (command) {
var rows = [],
am = priv.newAsyncModule(), o = {};
o.getContent = function (file) {
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' +
priv.secureDocId(file.id) + '?_=' + Date.now(),
type: "GET",
async: true,
dataType: 'text', // TODO : is it necessary ?
headers: {'Authorization':'Basic '+
Base64.encode(priv.username +':'+
priv.password)},
success: function (content) {
file.value.content = content;
// WARNING : files can be disordered because
// of asynchronous action
rows.push (file);
am.call(o,'success');
},
error: function (type) {
type.error = type.statusText; // TODO : to lower case
type.reason = 'Cannot get a document '+
'content from DAVStorage';
type.message = type.message + '.';
am.call(o,'error',[type]);
}
});
};
o.getDocumentList = function () {
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' + '?_=' + Date.now(),
async: true,
type: 'PROPFIND',
dataType: 'xml',
headers: {'Authorization': 'Basic '+Base64.encode(
priv.username + ':' + priv.password ), Depth: '1'},
// xhrFields: {withCredentials: 'true'}, // cross domain
success: function (xmlData) {
var response = $(xmlData).find(
'D\\:response, response'
);
var len = response.length;
if (len === 1) {
return am.call(o,'success');
} else {
am.wait(o,'success',len-2);
}
response.each( function(i,data){
if(i>0) { // exclude parent folder
var file = {value:{}};
$(data).find('D\\:href, href').each(function(){
var split = $(this).text().split('/');
file.id = split[split.length-1];
file.id = priv.restoreSlashes(file.id);
file.key = file.id;
});
if (file.id === '.htaccess' ||
file.id === '.htpasswd') { return; }
$(data).find(
'lp1\\:getlastmodified, getlastmodified'
).each(function () {
file.value._last_modified =
new Date($(this).text()).getTime();
});
$(data).find(
'lp1\\:creationdate, creationdate'
).each(function () {
file.value._creation_date =
new Date($(this).text()).getTime();
});
if (!command.getOption ('metadata_only')) {
am.call(o,'getContent',[file]);
} else {
rows.push (file);
am.call(o,'success');
}
}
});
},
error: function (type) {
if (type.status === 404) {
type.error = 'not_found';
type.reason = 'missing';
am.call(o,'error',[type]);
} else {
type.error = type.statusText; // TODO : to lower case
type.reason =
'Cannot get a document list from DAVStorage';
type.message = type.reason + '.';
am.call(o,'retry',[type]);
}
}
} );
};
o.retry = function (error) {
am.neverCall(o,'retry');
am.neverCall(o,'success');
am.neverCall(o,'error');
that.retry(error);
};
o.error = function (error) {
am.neverCall(o,'retry');
am.neverCall(o,'success');
am.neverCall(o,'error');
that.error(error);
};
o.success = function () {
am.neverCall(o,'retry');
am.neverCall(o,'success');
am.neverCall(o,'error');
that.success({total_rows:rows.length,
rows:rows});
};
am.call (o,'getDocumentList');
}; // end allDocs
/**
* Removes a document from a distant dav storage.
* @method remove
*/
that.remove = function (command) {
var secured_docid = priv.secureDocId(command.getDocId());
$.ajax ( {
url: priv.url + '/' +
priv.secured_username + '/' +
priv.secured_applicationname + '/' +
secured_docid + '?_=' + Date.now(),
type: "DELETE",
async: true,
headers: {'Authorization':'Basic '+Base64.encode(
priv.username + ':' + priv.password )},
// xhrFields: {withCredentials: 'true'}, // cross domain
success: function (data,state,type) {
that.success({ok:true,id:command.getDocId()});
},
error: function (type,state,statusText) {
if (type.status === 404) {
//that.success({ok:true,id:command.getDocId()});
type.error = 'not_found';
type.reason = 'missing';
type.message = 'Cannot remove missing file.';
that.error(type);
} else {
type.reason = 'Cannot remove "' + that.getDocId() + '"';
type.message = type.reason + '.';
that.retry(type);
}
}
} );
};
return that;
};
jIO.addStorageType('dav', newDAVStorage);
var newIndexStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
var validatestate_secondstorage = spec.storage || false;
priv.secondstorage_spec = spec.storage || {type:'base'};
priv.secondstorage_string = JSON.stringify (priv.secondstorage_spec);
var storage_object_name = 'jio/indexed_storage_object';
var storage_file_object_name = 'jio/indexed_file_object/'+
priv.secondstorage_string;
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.storage = priv.secondstorage_spec;
return o;
};
that.validateState = function () {
if (!validatestate_secondstorage) {
return 'Need at least one parameter: "storage" '+
'containing storage specifications.';
}
return '';
};
priv.secureDocId = function (string) {
var split = string.split('/'), i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i+= 1) {
if (split[i] === '') { return ''; }
}
return split.join('%2F');
};
priv.indexStorage = function () {
var obj = LocalOrCookieStorage.getItem (storage_object_name) || {};
obj[priv.secondstorage_spec] = new Date().getTime();
LocalOrCookieStorage.setItem (storage_object_name,obj);
};
priv.formatToFileObject = function (row) {
var k, obj = {_id:row.id};
for (k in row.value) {
obj[k] = row.value[k];
}
return obj;
};
priv.allDocs = function (files_object) {
var k, obj = {rows:[]}, i = 0;
for (k in files_object) {
obj.rows[i] = {};
obj.rows[i].value = files_object[k];
obj.rows[i].id = obj.rows[i].key = obj.rows[i].value._id;
delete obj.rows[i].value._id;
i ++;
}
obj.total_rows = obj.rows.length;
return obj;
};
priv.setFileArray = function (file_array) {
var i, obj = {};
for (i = 0; i < file_array.length; i+= 1) {
obj[file_array[i].id] = priv.formatToFileObject(file_array[i]);
}
LocalOrCookieStorage.setItem (storage_file_object_name,obj);
};
priv.getFileObject = function (docid) {
var obj = LocalOrCookieStorage.getItem (storage_file_object_name) || {};
return obj[docid];
};
priv.addFile = function (file_obj) {
var obj = LocalOrCookieStorage.getItem (storage_file_object_name) || {};
obj[file_obj._id] = file_obj;
LocalOrCookieStorage.setItem (storage_file_object_name,obj);
};
priv.removeFile = function (docid) {
var obj = LocalOrCookieStorage.getItem (storage_file_object_name) || {};
delete obj[docid];
LocalOrCookieStorage.setItem (storage_file_object_name,obj);
};
/**
* updates the storage.
* It will retreive all files from a storage. It is an asynchronous task
* so the update can be on going even if IndexedStorage has already
* returned the result.
* @method update
*/
priv.update = function () {
var success = function (val) {
priv.setFileArray(val.rows);
};
that.addJob ('allDocs', priv.secondstorage_spec,null,
{max_retry:3},success,function(){});
};
that.post = function (command) {
that.put(command);
};
/**
* Saves a document.
* @method put
*/
that.put = function (command) {
var cloned_doc = command.cloneDoc(),
cloned_option = command.cloneOption(),
success = function (val) {
priv.update();
that.success(val);
},
error = function (err) {
that.error(err);
};
priv.indexStorage();
that.addJob ('put',priv.secondstorage_spec,cloned_doc,
cloned_option,success,error);
}; // end put
/**
* Loads a document.
* @method get
*/
that.get = function (command) {
var file_array,
success = function (val) {
that.success(val);
},
error = function (err) {
that.error(err);
},
get = function () {
var cloned_option = command.cloneOption();
that.addJob ('get',priv.secondstorage_spec,command.cloneDoc(),
cloned_option,success,error);
that.end();
};
priv.indexStorage();
priv.update();
if (command.getOption('metadata_only')) {
setTimeout(function () {
var file_obj = priv.getFileObject(command.getDocId());
if (file_obj &&
(file_obj._last_modified ||
file_obj._creation_date)) {
that.success (file_obj);
} else {
get();
}
});
} else {
get();
}
}; // end get
/**
* Gets a document list.
* @method allDocs
*/
that.allDocs = function (command) {
var obj = LocalOrCookieStorage.getItem (storage_file_object_name);
if (obj) {
priv.update();
setTimeout(function (){
that.success (priv.allDocs(obj));
});
} else {
var success = function (val) {
priv.setFileArray(val.rows);
that.success(val);
},
error = function (err) {
that.error(err);
};
that.addJob ('allDocs', priv.secondstorage_spec,null,
command.cloneOption(),success,error);
}
}; // end allDocs
/**
* Removes a document.
* @method remove
*/
that.remove = function (command) {
var success = function (val) {
priv.removeFile(command.getDocId());
priv.update();
that.success(val);
},
error = function (err) {
that.error(err);
};
that.addJob ('remove',priv.secondstorage_spec,command.cloneDoc(),
command.cloneOption(),success,error);
}; // end remove
return that;
};
jIO.addStorageType ('indexed', newIndexStorage);
/**
* Adds 6 storages to JIO.
* - LocalStorage ('local')
* - DAVStorage ('dav')
* - ReplicateStorage ('replicate')
* - IndexedStorage ('indexed')
* - CryptedStorage ('crypted')
* - ConflictManagerStorage ('conflictmanager')
*
* @module JIOStorages
*/
(function(LocalOrCookieStorage, $, Base64, sjcl, hex_sha256, jIO) {
/**
* JIO Local Storage. Type = 'local'.
* It is a database located in the browser local storage.
*/
var newLocalStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
priv.secureDocId = function (string) {
var split = string.split('/'), i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i+= 1) {
if (split[i] === '') { return ''; }
}
return split.join('%2F');
};
priv.convertSlashes = function (string) {
return string.split('/').join('%2F');
};
priv.restoreSlashes = function (string) {
return string.split('%2F').join('/');
};
priv.username = spec.username || '';
priv.secured_username = priv.convertSlashes(priv.username);
priv.applicationname = spec.applicationname || 'untitled';
priv.secured_applicationname = priv.convertSlashes(priv.applicationname);
var storage_user_array_name = 'jio/local_user_array';
var storage_file_array_name = 'jio/local_file_name_array/' +
priv.secured_username + '/' + priv.secured_applicationname;
var super_serialized = that.serialized;
that.serialized = function() {
var o = super_serialized();
o.applicationname = priv.applicationname;
o.username = priv.username;
return o;
};
that.validateState = function() {
if (priv.secured_username) {
return '';
}
return 'Need at least one parameter: "username".';
};
/**
* Returns a list of users.
* @method getUserArray
* @return {array} The list of users.
*/
priv.getUserArray = function () {
return LocalOrCookieStorage.getItem(
storage_user_array_name) || [];
};
/**
* Adds a user to the user list.
* @method addUser
* @param {string} user_name The user name.
*/
priv.addUser = function (user_name) {
var user_array = priv.getUserArray();
user_array.push(user_name);
LocalOrCookieStorage.setItem(storage_user_array_name,
user_array);
};
/**
* checks if a user exists in the user array.
* @method userExists
* @param {string} user_name The user name
* @return {boolean} true if exist, else false
*/
priv.userExists = function (user_name) {
var user_array = priv.getUserArray(), i, l;
for (i = 0, l = user_array.length; i < l; i += 1) {
if (user_array[i] === user_name) {
return true;
}
}
return false;
};
/**
* Returns the file names of all existing files owned by the user.
* @method getFileNameArray
* @return {array} All the existing file paths.
*/
priv.getFileNameArray = function () {
return LocalOrCookieStorage.getItem(
storage_file_array_name) || [];
};
/**
* Adds a file name to the local file name array.
* @method addFileName
* @param {string} file_name The new file name.
*/
priv.addFileName = function (file_name) {
var file_name_array = priv.getFileNameArray();
file_name_array.push(file_name);
LocalOrCookieStorage.setItem(storage_file_array_name,
file_name_array);
};
/**
* Removes a file name from the local file name array.
* @method removeFileName
* @param {string} file_name The file name to remove.
*/
priv.removeFileName = function (file_name) {
var i, l, array = priv.getFileNameArray(), new_array = [];
for (i = 0, l = array.length; i < l; i+= 1) {
if (array[i] !== file_name) {
new_array.push(array[i]);
}
}
LocalOrCookieStorage.setItem(storage_file_array_name,
new_array);
};
priv.checkSecuredDocId = function (secured_docid,docid,method) {
if (!secured_docid) {
that.error({
status:403,statusText:'Method Not Allowed',
error:'method_not_allowed',
message:'Cannot '+method+' "'+docid+
'", file name is incorrect.',
reason:'Cannot '+method+' "'+docid+
'", file name is incorrect'
});
return false;
}
return true;
};
that.post = function (command) {
that.put(command);
};
/**
* Saves a document in the local storage.
* It will store the file in 'jio/local/USR/APP/FILE_NAME'.
* @method put
*/
that.put = function (command) {
// wait a little in order to simulate asynchronous saving
setTimeout (function () {
var secured_docid = priv.secureDocId(command.getDocId()),
doc = null, path =
'jio/local/'+priv.secured_username+'/'+
priv.secured_applicationname+'/'+
secured_docid;
if (!priv.checkSecuredDocId(
secured_docid,command.getDocId(),'put')) {return;}
// reading
doc = LocalOrCookieStorage.getItem(path);
if (!doc) {
// create document
doc = {
_id: command.getDocId(),
content: command.getDocContent(),
_creation_date: Date.now(),
_last_modified: Date.now()
};
if (!priv.userExists(priv.secured_username)) {
priv.addUser (priv.secured_username);
}
priv.addFileName(secured_docid);
} else {
// overwriting
doc.content = command.getDocContent();
doc._last_modified = Date.now();
}
LocalOrCookieStorage.setItem(path, doc);
that.success ({ok:true,id:command.getDocId()});
});
}; // end put
/**
* Loads a document from the local storage.
* It will load file in 'jio/local/USR/APP/FILE_NAME'.
* You can add an 'options' object to the job, it can contain:
* - metadata_only {boolean} default false, retrieve the file metadata
* only if true.
* @method get
*/
that.get = function (command) {
setTimeout(function () {
var secured_docid = priv.secureDocId(command.getDocId()),
doc = null;
if (!priv.checkSecuredDocId(
secured_docid,command.getDocId(),'get')) {return;}
doc = LocalOrCookieStorage.getItem(
'jio/local/'+priv.secured_username+'/'+
priv.secured_applicationname+'/'+secured_docid);
if (!doc) {
that.error ({status:404,statusText:'Not Found.',
error:'not_found',
message:'Document "'+ command.getDocId() +
'" not found.',
reason:'missing'});
} else {
if (command.getOption('metadata_only')) {
delete doc.content;
}
that.success (doc);
}
});
}; // end get
/**
* Gets a document list from the local storage.
* It will retreive an array containing files meta data owned by
* the user.
* @method allDocs
*/
that.allDocs = function (command) {
setTimeout(function () {
var new_array = [], array = [], i, l, k = 'key',
path = 'jio/local/'+priv.secured_username+'/'+
priv.secured_applicationname, file_object = {};
array = priv.getFileNameArray();
for (i = 0, l = array.length; i < l; i += 1) {
file_object =
LocalOrCookieStorage.getItem(path+'/'+array[i]);
if (file_object) {
if (command.getOption('metadata_only')) {
new_array.push ({
id:file_object._id,key:file_object._id,value:{
_creation_date:file_object._creation_date,
_last_modified:file_object._last_modified}});
} else {
new_array.push ({
id:file_object._id,key:file_object._id,value:{
content:file_object.content,
_creation_date:file_object._creation_date,
_last_modified:file_object._last_modified}});
}
}
}
that.success ({total_rows:new_array.length,rows:new_array});
});
}; // end allDocs
/**
* Removes a document from the local storage.
* It will also remove the path from the local file array.
* @method remove
*/
that.remove = function (command) {
setTimeout (function () {
var secured_docid = priv.secureDocId(command.getDocId()),
path = 'jio/local/'+
priv.secured_username+'/'+
priv.secured_applicationname+'/'+
secured_docid;
if (!priv.checkSecuredDocId(
secured_docid,command.getDocId(),'remove')) {return;}
// deleting
LocalOrCookieStorage.deleteItem(path);
priv.removeFileName(secured_docid);
that.success ({ok:true,id:command.getDocId()});
});
}; // end remove
return that;
};
jIO.addStorageType('local', newLocalStorage);
}( LocalOrCookieStorage, jQuery, Base64, sjcl, hex_sha256, jIO ));
var newReplicateStorage = function ( spec, my ) {
spec = spec || {};
var that = my.basicStorage( spec, my ), priv = {};
priv.return_value_array = [];
priv.storagelist = spec.storagelist || [];
priv.nb_storage = priv.storagelist.length;
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.storagelist = priv.storagelist;
return o;
};
that.validateState = function () {
if (priv.storagelist.length === 0) {
return 'Need at least one parameter: "storagelist" '+
'containing at least one storage.';
}
return '';
};
priv.isTheLast = function (error_array) {
return (error_array.length === priv.nb_storage);
};
priv.doJob = function (command,errormessage,nodocid) {
var done = false, error_array = [], i,
error = function (err) {
if (!done) {
error_array.push(err);
if (priv.isTheLast(error_array)) {
that.error ({
status:207,
statusText:'Multi-Status',
error:'multi_status',
message:'All '+errormessage+
(!nodocid?' "'+command.getDocId()+'"':' ') +
' requests have failed.',
reason:'requests fail',
array:error_array
});
}
}
},
success = function (val) {
if (!done) {
done = true;
that.success (val);
}
};
for (i = 0; i < priv.nb_storage; i+= 1) {
var cloned_option = command.cloneOption();
that.addJob (command.getLabel(),priv.storagelist[i],
command.cloneDoc(),cloned_option,success,error);
}
};
that.post = function (command) {
priv.doJob (command,'post');
that.end();
};
/**
* Save a document in several storages.
* @method put
*/
that.put = function (command) {
priv.doJob (command,'put');
that.end();
};
/**
* Load a document from several storages, and send the first retreived
* document.
* @method get
*/
that.get = function (command) {
priv.doJob (command,'get');
that.end();
};
/**
* Get a document list from several storages, and returns the first
* retreived document list.
* @method allDocs
*/
that.allDocs = function (command) {
priv.doJob (command,'allDocs',true);
that.end();
};
/**
* Remove a document from several storages.
* @method remove
*/
that.remove = function (command) {
priv.doJob (command,'remove');
that.end();
};
return that;
};
jIO.addStorageType('replicate', newReplicateStorage);
(function () { var jioWaitStorageLoader = function ( jIO ) {
var newWaitStorage = function ( spec, my ) {
var that = my.basicStorage( spec, my ), priv = {};
var validatestate_secondstorage = spec.storage || false;
priv.secondstorage_spec = spec.storage || {type:'base'};
priv.delay = spec.delay || 5000;
priv.save = spec.save || true;
priv.load = spec.load || false;
priv.getlist = spec.getlist || false;
priv.remove = spec.remove || false;
that.validateState = function () {
if (!validatestate_secondstorage) {
return 'Need at least one parameter: "storage" '+
'containing storage specifications.';
}
return '';
};
var super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized();
o.delay = priv.delay;
o.storage = priv.secondstorage_spec;
o.save = priv.save;
o.load = priv.load;
o.getlist = priv.getlist;
o.remove = priv.remove;
return o;
};
priv.doJob = function (command,timeout_or_not_timeout) {
var delay = 0;
if (timeout_or_not_timeout) {
delay = priv.delay;
}
setTimeout (function () {
that.addJob ( that.newStorage(priv.secondstorage_spec),
command );
that.end();
}, delay);
};
that.saveDocument = function (command) {
priv.doJob (command,priv.save);
}; // end saveDocument
that.loadDocument = function (command) {
priv.doJob (command,priv.load);
}; // end loadDocument
that.getDocumentList = function (command) {
priv.doJob (command,priv.getlist);
}; // end getDocumentList
that.removeDocument = function (command) {
priv.doJob (command,priv.remove);
}; // end removeDocument
return that;
};
Jio.addStorageType('wait', newWaitStorage);
};
if (window.requirejs) {
define ('JIOWaitStorages',['jIO'], jioWaitStorageLoader);
} else {
jioWaitStorageLoader ( jIO );
}
}());
auto: grunt
check-syntax: grunt
grunt:
make -C ../../grunt/*_gruntJIO > /dev/null 2>&1
var activityUpdater = (function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
priv.id = spec.id || 0;
priv.interval = 400;
priv.interval_id = null;
// Methods //
/**
* Update the last activity date in the localStorage.
* @method touch
*/
priv.touch = function() {
LocalOrCookieStorage.setItem ('jio/id/'+priv.id, Date.now());
};
/**
* Sets the jio id into the activity.
* @method setId
* @param {number} id The jio id.
*/
that.setId = function(id) {
priv.id = id;
};
/**
* Sets the interval delay between two updates.
* @method setIntervalDelay
* @param {number} ms In milliseconds
*/
that.setIntervalDelay = function(ms) {
priv.interval = ms;
};
/**
* Gets the interval delay.
* @method getIntervalDelay
* @return {number} The interval delay.
*/
that.getIntervalDelay = function() {
return priv.interval;
};
/**
* Starts the activity updater. It will update regulary the last activity
* date in the localStorage to show to other jio instance that this instance
* is active.
* @method start
*/
that.start = function() {
if (!priv.interval_id) {
priv.touch();
priv.interval_id = setInterval(function() {
priv.touch();
}, priv.interval);
}
};
/**
* Stops the activity updater.
* @method stop
*/
that.stop = function() {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
}
};
return that;
}());
var announcement = function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
var callback_a = [];
var name = spec.name || '';
var announcer = spec.announcer || {};
// Methods //
that.add = function(callback) {
callback_a.push(callback);
};
that.remove = function(callback) {
var i, tmp_callback_a = [];
for (i = 0; i < callback_a.length; i+= 1) {
if (callback_a[i] !== callback) {
tmp_callback_a.push(callback_a[i]);
}
}
callback_a = tmp_callback_a;
};
that.register = function() {
announcer.register(that);
};
that.unregister = function() {
announcer.unregister(that);
};
that.trigger = function(args) {
var i;
for(i = 0; i < callback_a.length; i++) {
callback_a[i].apply(null, args);
}
};
return that;
};
var announcer = (function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
var announcement_o = {};
// Methods //
that.register = function(name) {
if(!announcement_o[name]) {
announcement_o[name] = announcement();
}
};
that.unregister = function(name) {
if (announcement_o[name]) {
delete announcement_o[name];
}
};
that.at = function(name) {
return announcement_o[name];
};
that.on = function(name, callback) {
that.register(name);
that.at(name).add(callback);
};
that.trigger = function(name, args) {
that.at(name).trigger(args);
};
return that;
}());
var allDocsCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'allDocs';
};
that.executeOn = function(storage) {
storage.allDocs (that);
};
that.canBeRestored = function() {
return false;
};
that.validateState = function () {
return true;
};
return that;
};
var command = function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
priv.commandlist = {'post':postCommand,
'put':putCommand,
'get':getCommand,
'remove':removeCommand,
'allDocs':allDocsCommand};
// creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) {
priv.label = spec.label;
delete spec.label;
return priv.commandlist[priv.label](spec, my);
}
priv.tried = 0;
priv.doc = spec.doc || {};
priv.docid = spec.docid || '';
priv.option = spec.options || {};
priv.callbacks = spec.callbacks || {};
priv.success = priv.callbacks.success || function (){};
priv.error = priv.callbacks.error || function (){};
priv.retry = function() {
that.error({
status:13,statusText:'Fail Retry',error:'fail_retry',
message:'Impossible to retry.',reason:'Impossible to retry.'
});
};
priv.end = function() {};
priv.on_going = false;
// Methods //
/**
* Returns a serialized version of this command.
* Override this function.
* @method serialized
* @return {object} The serialized command.
*/
that.serialized = function() {
return {label:that.getLabel(),
tried:priv.tried,
doc:that.cloneDoc(),
option:that.cloneOption()};
};
/**
* Returns the label of the command.
* @method getLabel
* @return {string} The label.
*/
that.getLabel = function() {
return 'command';
};
that.getDocId = function () {
return priv.docid || priv.doc._id;
};
that.getDocContent = function () {
return priv.doc.content;
};
/**
* Returns an information about the document.
* @method getDocInfo
* @param {string} infoname The info name.
* @return The info value.
*/
that.getDocInfo = function (infoname) {
return priv.doc[infoname];
};
/**
* Returns the value of an option.
* @method getOption
* @param {string} optionname The option name.
* @return The option value.
*/
that.getOption = function (optionname) {
return priv.option[optionname];
};
/**
* Validates the storage.
* @param {object} storage The storage.
*/
that.validate = function (storage) {
if (!that.validateState()) {
return false;
}
return storage.validate();
};
/*
* Extend this function
*/
that.validateState = function() {
if (typeof priv.doc !== 'object') {
that.error({
status:20,
statusText:'Document_Id Required',
error:'document_id_required',
message:'No document id.',
reason:'no document id'
});
return false;
}
return true;
};
that.canBeRetried = function () {
return (typeof priv.option.max_retry === 'undefined' ||
priv.option.max_retry === 0 ||
priv.tried < priv.option.max_retry);
};
that.getTried = function() {
return priv.tried;
};
/**
* Delegate actual excecution the storage.
* @param {object} storage The storage.
*/
that.execute = function(storage) {
if (!priv.on_going) {
if (that.validate (storage)) {
priv.tried ++;
priv.on_going = true;
storage.execute (that);
}
}
};
/**
* Execute the good method from the storage.
* Override this function.
* @method executeOn
* @param {object} storage The storage.
*/
that.executeOn = function(storage) {};
that.success = function(return_value) {
priv.on_going = false;
priv.success (return_value);
priv.end(doneStatus());
};
that.retry = function (return_error) {
priv.on_going = false;
if (that.canBeRetried()) {
priv.retry();
} else {
that.error (return_error);
}
};
that.error = function(return_error) {
priv.on_going = false;
priv.error(return_error);
priv.end(failStatus());
};
that.end = function () {
priv.end(doneStatus());
};
that.onSuccessDo = function (fun) {
if (fun) {
priv.success = fun;
} else {
return priv.success;
}
};
that.onErrorDo = function (fun) {
if (fun) {
priv.error = fun;
} else {
return priv.error;
}
};
that.onEndDo = function (fun) {
priv.end = fun;
};
that.onRetryDo = function (fun) {
priv.retry = fun;
};
/**
* Is the command can be restored by another JIO : yes.
* @method canBeRestored
* @return {boolean} true
*/
that.canBeRestored = function() {
return true;
};
/**
* Clones the command and returns it.
* @method clone
* @return {object} The cloned command.
*/
that.clone = function () {
return command(that.serialized(), my);
};
/**
* Clones the command options and returns the clone version.
* @method cloneOption
* @return {object} The clone of the command options.
*/
that.cloneOption = function () {
var k, o = {};
for (k in priv.option) {
o[k] = priv.option[k];
}
return o;
};
/**
* Clones the document and returns the clone version.
* @method cloneDoc
* @return {object} The clone of the document.
*/
that.cloneDoc = function () {
if (priv.docid) {
return priv.docid;
}
var k, o = {};
for (k in priv.doc) {
o[k] = priv.doc[k];
}
return o;
};
return that;
};
var getCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'get';
};
that.validateState = function() {
if (!that.getDocId()) {
that.error({
status:20,statusText:'Document Id Required',
error:'document_id_required',
message:'No document id.',reason:'no document id'
});
return false;
}
return true;
};
that.executeOn = function(storage) {
storage.get (that);
};
that.canBeRestored = function() {
return false;
};
return that;
};
var postCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
// Methods //
that.getLabel = function() {
return 'post';
};
/**
* Validates the storage handler.
* @param {object} handler The storage handler
*/
that.validate = function () {
if (typeof that.getDocInfo('content') !== 'string') {
that.error({
status:21,statusText:'Content Required',
error:'content_required',
message:'No data to put.',reason:'no data to put'
});
return false;
}
return that.validateState();
};
that.executeOn = function(storage) {
storage.put (that);
};
return that;
};
var putCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
// Methods //
that.getLabel = function() {
return 'put';
};
/**
* Validates the storage handler.
* @param {object} handler The storage handler
*/
that.validate = function () {
if (typeof that.getDocInfo('content') !== 'string') {
that.error({
status:21,statusText:'Content Required',
error:'content_required',
message:'No data to put.',reason:'no data to put'
});
return false;
}
return that.validateState();
};
that.executeOn = function(storage) {
storage.put (that);
};
return that;
};
var removeCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'remove';
};
that.executeOn = function(storage) {
storage.remove (that);
};
return that;
};
var jioException = function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
that.name = 'jioException';
that.message = spec.message || 'Unknown Reason.';
that.toString = function() {
return that.name + ': ' + that.message;
};
return that;
};
var invalidCommandState = function(spec, my) {
var that = jioException(spec, my);
spec = spec || {};
var command = spec.command;
that.name = 'invalidCommandState';
that.toString = function() {
return that.name +': ' +
command.getLabel() + ', ' + that.message;
};
return that;
};
var invalidStorage = function(spec, my) {
var that = jioException(spec, my);
spec = spec || {};
var type = spec.storage.getType();
that.name = 'invalidStorage';
that.toString = function() {
return that.name +': ' +
'Type "'+type + '", ' + that.message;
};
return that;
};
var invalidStorageType = function(spec, my) {
var that = jioException(spec, my);
var type = spec.type;
that.name = 'invalidStorageType';
that.toString = function() {
return that.name +': ' +
type + ', ' + that.message;
};
return that;
};
var jobNotReadyException = function(spec, my) {
var that = jioException(spec, my);
that.name = 'jobNotReadyException';
return that;
};
var tooMuchTriesJobException = function(spec, my) {
var that = jioException(spec, my);
that.name = 'tooMuchTriesJobException';
return that;
};
var invalidJobException = function(spec, my) {
var that = jioException(spec, my);
that.name = 'invalidJobException';
return that;
};
var jIO = (function () {
"use strict";
var jio = function(spec) {
// Class jio
var that = {}, priv = {};
spec = spec || {};
// Attributes //
var jio_id_array_name = 'jio/id_array';
priv.id = null;
priv.storage_spec = spec;
// initialize //
priv.init = function() {
// Initialize the jio id and add the new id to the list
if (priv.id === null) {
var i, jio_id_a =
LocalOrCookieStorage.getItem (jio_id_array_name) || [];
priv.id = 1;
for (i = 0; i < jio_id_a.length; i+= 1) {
if (jio_id_a[i] >= priv.id) {
priv.id = jio_id_a[i] + 1;
}
}
jio_id_a.push(priv.id);
LocalOrCookieStorage.setItem (jio_id_array_name,jio_id_a);
activityUpdater.setId(priv.id);
jobManager.setId(priv.id);
}
};
// Methods //
/**
* Returns a storage from a storage description.
* @method storage
* @param {object} spec The specifications.
* @param {object} my The protected object.
* @param {string} forcetype Force storage type
* @return {object} The storage object.
*/
Object.defineProperty(that,"storage",{
configurable:false,enumerable:false,writable:false,value:
function(spec, my, forcetype) {
spec = spec || {};
my = my || {};
my.basicStorage = storage;
my.storage = that.storage; // NOTE : or proxy storage
var type = forcetype || spec.type || 'base';
if (type === 'base') {
return storage(spec, my);
}
if (!storage_type_object[type]) {
throw invalidStorageType(
{type:type,message:'Storage does not exists.'});
}
return storage_type_object[type](spec, my);
}
});
jobManager.storage = that.storage;
Object.defineProperty(that,"start",{
configurable:false,enumerable:false,writable:false,value:
function() {
priv.init();
activityUpdater.start();
jobManager.start();
}
});
Object.defineProperty(that,"stop",{
configurable:false,enumerable:false,writable:false,value:
function() {
jobManager.stop();
}
});
Object.defineProperty(that,"close",{
configurable:false,enumerable:false,writable:false,value:
function() {
activityUpdater.stop();
jobManager.stop();
priv.id = null;
}
});
/**
* Returns the jio id.
* @method getId
* @return {number} The jio id.
*/
Object.defineProperty(that,"getId",{
configurable:false,enumerable:false,writable:false,value:
function() {
return priv.id;
}
});
/**
* Returns the jio job rules object used by the job manager.
* @method getJobRules
* @return {object} The job rules object
*/
Object.defineProperty(that,"getJobRules",{
configurable:false,enumerable:false,writable:false,value:
function() {
return jobRules;
}
});
/**
* Checks if the storage description is valid or not.
* @method validateStorageDescription
* @param {object} description The description object.
* @return {boolean} true if ok, else false.
*/
Object.defineProperty(that,"validateStorageDescription",{
configurable:false,enumerable:false,writable:false,value:
function(description) {
return that.storage(description).isValid();
}
});
Object.defineProperty(that,"getJobArray",{
configurable:false,enumerable:false,writable:false,value:
function () {
return jobManager.serialized();
}
});
priv.getParam = function (list,nodoc) {
var param = {}, i = 0;
if (!nodoc) {
param.doc = list[i];
i ++;
}
if (typeof list[i] === 'object') {
param.options = list[i];
i ++;
} else {
param.options = {};
}
param.callback = function (err,val){};
param.success = function (val) {
param.callback(undefined,val);
};
param.error = function (err) {
param.callback(err,undefined);
};
if (typeof list[i] === 'function') {
if (typeof list[i+1] === 'function') {
param.success = list[i];
param.error = list[i+1];
} else {
param.callback = list[i];
}
}
return param;
};
priv.addJob = function (commandCreator,spec) {
jobManager.addJob(
job({storage:that.storage(priv.storage_spec),
command:commandCreator(spec)}));
};
/**
* Post a document.
* @method post
* @param {object} doc The document {"content":}.
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that,"post",{
configurable:false,enumerable:false,writable:false,value:
function() {
var param = priv.getParam(arguments);
param.options.max_retry = param.options.max_retry || 0;
priv.addJob(postCommand,{
doc:param.doc,
options:param.options,
callbacks:{success:param.success,error:param.error}
});
}
});
/**
* Put a document.
* @method put
* @param {object} doc The document {"_id":,"_rev":,"content":}.
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that,"put",{
configurable:false,enumerable:false,writable:false,value:
function() {
var param = priv.getParam(arguments);
param.options.max_retry = param.options.max_retry || 0;
priv.addJob(putCommand,{
doc:param.doc,
options:param.options,
callbacks:{success:param.success,error:param.error}
});
}
});
/**
* Get a document.
* @method get
* @param {string} docid The document id (the path).
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} metadata_only Load only document metadata.
* - {string} rev The revision we want to get.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that,"get",{
configurable:false,enumerable:false,writable:false,value:
function() {
var param = priv.getParam(arguments);
param.options.max_retry = param.options.max_retry || 3;
param.options.metadata_only = (
param.options.metadata_only !== undefined?
param.options.metadata_only:false);
priv.addJob(getCommand,{
docid:param.doc,
options:param.options,
callbacks:{success:param.success,error:param.error}
});
}
});
/**
* Remove a document.
* @method remove
* @param {object} doc The document {"_id":,"_rev":}.
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that,"remove",{
configurable:false,enumerable:false,writable:false,value:
function() {
var param = priv.getParam(arguments);
param.options.max_retry = param.options.max_retry || 0;
priv.addJob(removeCommand,{
doc:param.doc,
options:param.options,
callbacks:{success:param.success,error:param.error}
});
}
});
/**
* Get a list of documents.
* @method allDocs
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} metadata_only Load only document metadata
* - {boolean} descending Reverse the order of the output table.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include revisions.
* - {boolean} conflicts Include conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that,"allDocs",{
configurable:false,enumerable:false,writable:false,value:
function() {
var param = priv.getParam(arguments,'no doc');
param.options.max_retry = param.options.max_retry || 3;
param.options.metadata_only = (
param.options.metadata_only !== undefined?
param.options.metadata_only:true);
priv.addJob(allDocsCommand,{
options:param.options,
callbacks:{success:param.success,error:param.error}
});
}
});
return that;
}; // End Class jio
var storage_type_object = { // -> 'key':constructorFunction
'base': function () {} // overriden by jio
};
var jioNamespace = (function(spec) {
var that = {};
spec = spec || {};
// Attributes //
// Methods //
/**
* Creates a new jio instance.
* @method newJio
* @param {object} spec The parameters:
* - {object} spec.storage: A storage description
* - {string} spec.storage.type: The storage type
* - {string} spec.storage.username: The user name
* - {string} spec.storage.applicationname: The application name
* @return {object} The new Jio instance.
*/
Object.defineProperty(that,"newJio",{
configurable:false,enumerable:false,writable:false,value:
function(spec) {
var storage = spec, instance = null;
if (typeof storage === 'string') {
storage = JSON.parse (storage);
}
storage = storage || {type:'base'};
instance = jio(spec);
instance.start();
return instance;
}
});
/**
* Add a storage type to jio.
* @method addStorageType
* @param {string} type The storage type
* @param {function} constructor The associated constructor
*/
Object.defineProperty(that,"addStorageType",{
configurable:false,enumerable:false,writable:false,value:
function(type, constructor) {
constructor = constructor || function(){return null;};
if (storage_type_object[type]) {
throw invalidStorageType({type:type,message:'Already known.'});
}
storage_type_object[type] = constructor;
}
});
return that;
}());
var job = function(spec) {
var that = {};
spec = spec || {};
// Attributes //
var priv = {};
priv.id = jobIdHandler.nextId();
priv.command = spec.command;
priv.storage = spec.storage;
priv.status = initialStatus();
priv.date = new Date();
// Initialize //
if (!priv.storage){
throw invalidJobException({job:that,message:'No storage set'});
}
if (!priv.command){
throw invalidJobException({job:that,message:'No command set'});
}
// Methods //
/**
* Returns the job command.
* @method getCommand
* @return {object} The job command.
*/
that.getCommand = function() {
return priv.command;
};
that.getStatus = function() {
return priv.status;
};
that.getId = function() {
return priv.id;
};
that.getStorage = function() {
return priv.storage;
};
that.getDate = function() {
return priv.date;
};
/**
* Checks if the job is ready.
* @method isReady
* @return {boolean} true if ready, else false.
*/
that.isReady = function() {
if (priv.command.getTried() === 0) {
return priv.status.canStart();
} else {
return priv.status.canRestart();
}
};
/**
* Returns a serialized version of this job.
* @method serialized
* @return {object} The serialized job.
*/
that.serialized = function() {
return {id:priv.id,
date:priv.date.getTime(),
status:priv.status.serialized(),
command:priv.command.serialized(),
storage:priv.storage.serialized()};
};
/**
* Tells the job to wait for another one.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function(job) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForJob(job);
};
/**
* Tells the job to do not wait for a job.
* @method dontWaitForJob
* @param {object} job The other job.
*/
that.dontWaitFor = function(job) {
if (priv.status.getLabel() === 'wait') {
priv.status.dontWaitForJob(job);
}
};
/**
* Tells the job to wait for a while.
* @method waitForTime
* @param {number} ms Time to wait in millisecond.
*/
that.waitForTime = function(ms) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForTime(ms);
};
/**
* Tells the job to do not wait for a while anymore.
* @method stopWaitForTime
*/
that.stopWaitForTime = function() {
if (priv.status.getLabel() === 'wait') {
priv.status.stopWaitForTime();
}
};
that.eliminated = function () {
priv.command.error ({
status:10,statusText:'Stopped',error:'stopped',
message:'This job has been stopped by another one.',
reason:'this job has been stopped by another one'});
};
that.notAccepted = function () {
priv.command.onEndDo (function () {
priv.status = failStatus();
jobManager.terminateJob (that);
});
priv.command.error ({
status:11,statusText:'Not Accepted',error:'not_accepted',
message:'This job is already running.',
reason:'this job is already running'});
};
/**
* Updates the date of the job with the another one.
* @method update
* @param {object} job The other job.
*/
that.update = function(job) {
priv.command.error ({
status:12,statusText:'Replaced',error:'replaced',
message:'Job has been replaced by another one.',
reason:'job has been replaced by another one'});
priv.date = new Date(job.getDate().getTime());
priv.command = job.getCommand();
priv.status = job.getStatus();
};
/**
* Executes this job.
* @method execute
*/
that.execute = function() {
if (!that.getCommand().canBeRetried()) {
throw tooMuchTriesJobException(
{job:that,message:'The job was invoked too much time.'});
}
if (!that.isReady()) {
throw jobNotReadyException(
{job:that,message:'Can not execute this job.'});
}
priv.status = onGoingStatus();
priv.command.onRetryDo (function() {
var ms = priv.command.getTried();
ms = ms*ms*200;
if (ms>10000){
ms = 10000;
}
that.waitForTime(ms);
});
priv.command.onEndDo (function(status) {
priv.status = status;
jobManager.terminateJob (that);
});
priv.command.execute (priv.storage);
};
return that;
};
var jobIdHandler = (function(spec) {
var that = {};
spec = spec || {};
// Attributes //
var id = 0;
// Methods //
that.nextId = function() {
id = id + 1;
return id;
};
return that;
}());
var jobManager = (function(spec) {
var that = {};
spec = spec || {};
// Attributes //
var job_array_name = 'jio/job_array';
var priv = {};
priv.id = spec.id;
priv.interval_id = null;
priv.interval = 200;
priv.job_array = [];
// Methods //
/**
* Get the job array name in the localStorage
* @method getJobArrayName
* @return {string} The job array name
*/
priv.getJobArrayName = function() {
return job_array_name + '/' + priv.id;
};
/**
* Returns the job array from the localStorage
* @method getJobArray
* @return {array} The job array.
*/
priv.getJobArray = function() {
return LocalOrCookieStorage.getItem(priv.getJobArrayName())||[];
};
/**
* Does a backup of the job array in the localStorage.
* @method copyJobArrayToLocal
*/
priv.copyJobArrayToLocal = function() {
var new_a = [], i;
for (i = 0; i < priv.job_array.length; i+= 1) {
new_a.push(priv.job_array[i].serialized());
}
LocalOrCookieStorage.setItem(priv.getJobArrayName(),new_a);
};
/**
* Removes a job from the current job array.
* @method removeJob
* @param {object} job The job object.
*/
priv.removeJob = function(job) {
var i, tmp_job_array = [];
for (i = 0; i < priv.job_array.length; i+= 1) {
if (priv.job_array[i] !== job) {
tmp_job_array.push(priv.job_array[i]);
}
}
priv.job_array = tmp_job_array;
priv.copyJobArrayToLocal();
};
/**
* Sets the job manager id.
* @method setId
* @param {number} id The id.
*/
that.setId = function(id) {
priv.id = id;
};
/**
* Starts listening to the job array, executing them regulary.
* @method start
*/
that.start = function() {
var i;
if (priv.interval_id === null) {
priv.interval_id = setInterval (function() {
priv.restoreOldJio();
for (i = 0; i < priv.job_array.length; i+= 1) {
that.execute(priv.job_array[i]);
}
},priv.interval);
}
};
/**
* Stops listening to the job array.
* @method stop
*/
that.stop = function() {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
if (priv.job_array.length === 0) {
LocalOrCookieStorage.deleteItem(priv.getJobArrayName());
}
}
};
/**
* Try to restore an the inactive older jio instances.
* It will restore the on going or initial jobs from their job array
* and it will add them to this job array.
* @method restoreOldJio
*/
priv.restoreOldJio = function() {
var i, jio_id_a;
priv.lastrestore = priv.lastrestore || 0;
if (priv.lastrestore > (Date.now()) - 2000) { return; }
jio_id_a = LocalOrCookieStorage.getItem('jio/id_array')||[];
for (i = 0; i < jio_id_a.length; i+= 1) {
priv.restoreOldJioId(jio_id_a[i]);
}
priv.lastrestore = Date.now();
};
/**
* Try to restore an old jio according to an id.
* @method restoreOldJioId
* @param {number} id The jio id.
*/
priv.restoreOldJioId = function(id) {
var jio_date;
jio_date = LocalOrCookieStorage.getItem('jio/id/'+id)||0;
if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec
priv.restoreOldJobFromJioId(id);
priv.removeOldJioId(id);
priv.removeJobArrayFromJioId(id);
}
};
/**
* Try to restore all jobs from another jio according to an id.
* @method restoreOldJobFromJioId
* @param {number} id The jio id.
*/
priv.restoreOldJobFromJioId = function(id) {
var i, jio_job_array;
jio_job_array = LocalOrCookieStorage.getItem('jio/job_array/'+id)||[];
for (i = 0; i < jio_job_array.length; i+= 1) {
var command_object = command(jio_job_array[i].command);
if (command_object.canBeRestored()) {
that.addJob ( job(
{storage:that.storage(jio_job_array[i].storage),
command:command_object}));
}
}
};
/**
* Removes a jio instance according to an id.
* @method removeOldJioId
* @param {number} id The jio id.
*/
priv.removeOldJioId = function(id) {
var i, jio_id_array, new_array = [];
jio_id_array = LocalOrCookieStorage.getItem('jio/id_array')||[];
for (i = 0; i < jio_id_array.length; i+= 1) {
if (jio_id_array[i] !== id) {
new_array.push(jio_id_array[i]);
}
}
LocalOrCookieStorage.setItem('jio/id_array',new_array);
LocalOrCookieStorage.deleteItem('jio/id/'+id);
};
/**
* Removes a job array from a jio instance according to an id.
* @method removeJobArrayFromJioId
* @param {number} id The jio id.
*/
priv.removeJobArrayFromJioId = function(id) {
LocalOrCookieStorage.deleteItem('jio/job_array/'+id);
};
/**
* Executes a job.
* @method execute
* @param {object} job The job object.
*/
that.execute = function(job) {
try {
job.execute();
} catch (e) {
switch (e.name) {
case 'jobNotReadyException': break; // do nothing
case 'tooMuchTriesJobException': break; // do nothing
default: throw e;
}
}
priv.copyJobArrayToLocal();
};
/**
* Checks if a job exists in the job array according to a job id.
* @method jobIdExists
* @param {number} id The job id.
* @return {boolean} true if exists, else false.
*/
that.jobIdExists = function(id) {
var i;
for (i = 0; i < priv.job_array.length; i+= 1) {
if (priv.job_array[i].getId() === id) {
return true;
}
}
return false;
};
/**
* Terminate a job. It only remove it from the job array.
* @method terminateJob
* @param {object} job The job object
*/
that.terminateJob = function(job) {
priv.removeJob(job);
};
/**
* Adds a job to the current job array.
* @method addJob
* @param {object} job The new job.
*/
that.addJob = function(job) {
var result_array =
that.validateJobAccordingToJobList (priv.job_array,job);
priv.appendJob (job,result_array);
};
/**
* Generate a result array containing action string to do with the good job.
* @method validateJobAccordingToJobList
* @param {array} job_array A job array.
* @param {object} job The new job to compare with.
* @return {array} A result array.
*/
that.validateJobAccordingToJobList = function(job_array,job) {
var i, result_array = [];
for (i = 0; i < job_array.length; i+= 1) {
result_array.push(jobRules.validateJobAccordingToJob (job_array[i],job));
}
return result_array;
};
/**
* It will manage the job in order to know what to do thanks to a result
* array. The new job can be added to the job array, but it can also be
* not accepted. It is this method which can tells jobs to wait for another
* one, to replace one or to eliminate some while browsing.
* @method appendJob
* @param {object} job The job to append.
* @param {array} result_array The result array.
*/
priv.appendJob = function(job,result_array) {
var i;
if (priv.job_array.length !== result_array.length) {
throw new RangeError("Array out of bound");
}
for (i = 0; i < result_array.length; i+= 1) {
if (result_array[i].action === 'dont accept') {
return job.notAccepted();
}
}
for (i = 0; i < result_array.length; i+= 1) {
switch (result_array[i].action) {
case 'eliminate':
result_array[i].job.eliminated();
priv.removeJob(result_array[i].job);
break;
case 'update':
result_array[i].job.update(job);
priv.copyJobArrayToLocal();
return;
case 'wait':
job.waitForJob(result_array[i].job);
break;
default: break;
}
}
priv.job_array.push(job);
priv.copyJobArrayToLocal();
};
that.serialized = function () {
var a = [], i, job_array = priv.job_array || [];
for (i = 0; i < job_array.length; i+= 1) {
a.push(job_array[i].serialized());
}
return a;
};
return that;
}());
var jobRules = (function(spec) {
var that = {};
// Attributes //
var priv = {};
priv.compare = {};
priv.action = {};
Object.defineProperty(that,"eliminate",{
configurable:false,enumerable:false,writable:false,value:
function() { return 'eliminate'; }
});
Object.defineProperty(that,"update",{
configurable:false,enumerable:false,writable:false,value:
function() { return 'update'; }
});
Object.defineProperty(that,"dontAccept",{
configurable:false,enumerable:false,writable:false,value:
function() { return 'dont accept'; }
});
Object.defineProperty(that,"wait",{
configurable:false,enumerable:false,writable:false,value:
function() { return 'wait'; }
});
Object.defineProperty(that,"none",{
configurable:false,enumerable:false,writable:false,value:
function() { return 'none'; }
});
that.default_action = that.none;
that.default_compare = function(job1,job2) {
return (job1.getCommand().getDocId() ===
job2.getCommand().getDocId() &&
job1.getCommand().getDocInfo('_rev') ===
job2.getCommand().getDocInfo('_rev') &&
job1.getCommand().getOption('rev') ===
job2.getCommand().getOption('rev') &&
JSON.stringify(job1.getStorage().serialized()) ===
JSON.stringify(job2.getStorage().serialized()));
};
// Methods //
/**
* Returns an action according the jobs given in parameters.
* @method getAction
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {string} An action string.
*/
priv.getAction = function(job1,job2) {
var j1label, j2label, j1status;
j1label = job1.getCommand().getLabel();
j2label = job2.getCommand().getLabel();
j1status = (job1.getStatus().getLabel()==='on going'?
'on going':'not on going');
if (priv.action[j1label] &&
priv.action[j1label][j1status] &&
priv.action[j1label][j1status][j2label]) {
return priv.action[j1label][j1status][j2label](job1,job2);
} else {
return that.default_action(job1,job2);
}
};
/**
* Checks if the two jobs are comparable.
* @method canCompare
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {boolean} true if comparable, else false.
*/
priv.canCompare = function(job1,job2) {
var job1label = job1.getCommand().getLabel(),
job2label = job2.getCommand().getLabel();
if (priv.compare[job1label] &&
priv.compare[job2label]) {
return priv.compare[job1label][job2label](job1,job2);
} else {
return that.default_compare(job1,job2);
}
};
/**
* Returns an action string to show what to do if we want to add a job.
* @method validateJobAccordingToJob
* @param job1 {object} The current job.
* @param job2 {object} The new job.
* @return {string} The action string.
*/
Object.defineProperty(that,"validateJobAccordingToJob",{
configurable:false,enumerable:false,writable:false,value:
function(job1,job2) {
if (priv.canCompare(job1,job2)) {
return {action:priv.getAction(job1,job2),job:job1};
}
return {action:that.default_action(job1,job2),job:job1};
}
});
/**
* Adds a rule the action rules.
* @method addActionRule
* @param method1 {string} The action label from the current job.
* @param ongoing {boolean} Is this action is on going or not?
* @param method2 {string} The action label from the new job.
* @param rule {function} The rule that return an action string.
*/
Object.defineProperty(that,"addActionRule",{
configurable:false,enumerable:false,writable:false,value:
function(method1,ongoing,method2,rule) {
var ongoing_s = (ongoing?'on going':'not on going');
priv.action[method1] = priv.action[method1] || {};
priv.action[method1][ongoing_s] =
priv.action[method1][ongoing_s] || {};
priv.action[method1][ongoing_s][method2] = rule;
}
});
/**
* Adds a rule the compare rules.
* @method addCompareRule
* @param method1 {string} The action label from the current job.
* @param method2 {string} The action label from the new job.
* @param rule {function} The rule that return a boolean
* - true if job1 and job2 can be compared, else false.
*/
Object.defineProperty(that,"addCompareRule",{
configurable:false,enumerable:false,writable:false,value:
function(method1,method2,rule) {
priv.compare[method1] = priv.compare[method1] || {};
priv.compare[method1][method2] = rule;
}
});
////////////////////////////////////////////////////////////////////////////
// Adding some rules
/*
LEGEND:
- s: storage
- m: method
- n: name
- c: content
- o: options
- =: are equal
- !: are not equal
select ALL s= n=
removefailordone fail|done
/ elim repl nacc wait
Remove !ongoing Save 1 x x x
Save !ongoing Remove 1 x x x
GetList !ongoing GetList 0 1 x x
Remove !ongoing Remove 0 1 x x
Load !ongoing Load 0 1 x x
Save c= !ongoing Save 0 1 x x
Save c! !ongoing Save 0 1 x x
GetList ongoing GetList 0 0 1 x
Remove ongoing Remove 0 0 1 x
Remove ongoing Load 0 0 1 x
Remove !ongoing Load 0 0 1 x
Load ongoing Load 0 0 1 x
Save c= ongoing Save 0 0 1 x
Remove ongoing Save 0 0 0 1
Load ongoing Remove 0 0 0 1
Load ongoing Save 0 0 0 1
Load !ongoing Remove 0 0 0 1
Load !ongoing Save 0 0 0 1
Save ongoing Remove 0 0 0 1
Save ongoing Load 0 0 0 1
Save c! ongoing Save 0 0 0 1
Save !ongoing Load 0 0 0 1
GetList ongoing Remove 0 0 0 0
GetList ongoing Load 0 0 0 0
GetList ongoing Save 0 0 0 0
GetList !ongoing Remove 0 0 0 0
GetList !ongoing Load 0 0 0 0
GetList !ongoing Save 0 0 0 0
Remove ongoing GetList 0 0 0 0
Remove !ongoing GetList 0 0 0 0
Load ongoing GetList 0 0 0 0
Load !ongoing GetList 0 0 0 0
Save ongoing GetList 0 0 0 0
Save !ongoing GetList 0 0 0 0
For more information, see documentation
*/
var putput = function(job1,job2){
if (job1.getCommand().getDocInfo('content') ===
job2.getCommand().getDocInfo('content')) {
return that.dontAccept();
} else {
return that.wait();
}
};
that.addActionRule('post' ,true ,'post', putput);
that.addActionRule('post' ,true ,'put', putput);
that.addActionRule('post' ,true ,'get' ,that.wait);
that.addActionRule('post' ,true ,'remove' ,that.wait);
that.addActionRule('post' ,false,'post' ,that.update);
that.addActionRule('post' ,false,'put' ,that.update);
that.addActionRule('post' ,false,'get' ,that.wait);
that.addActionRule('post' ,false,'remove' ,that.eliminate);
that.addActionRule('put' ,true ,'post', putput);
that.addActionRule('put' ,true ,'put', putput);
that.addActionRule('put' ,true ,'get' ,that.wait);
that.addActionRule('put' ,true ,'remove' ,that.wait);
that.addActionRule('put' ,false,'post' ,that.update);
that.addActionRule('put' ,false,'put' ,that.update);
that.addActionRule('put' ,false,'get' ,that.wait);
that.addActionRule('put' ,false,'remove' ,that.eliminate);
that.addActionRule('get' ,true ,'post' ,that.wait);
that.addActionRule('get' ,true ,'put' ,that.wait);
that.addActionRule('get' ,true ,'get' ,that.dontAccept);
that.addActionRule('get' ,true ,'remove' ,that.wait);
that.addActionRule('get' ,false,'post' ,that.wait);
that.addActionRule('get' ,false,'put' ,that.wait);
that.addActionRule('get' ,false,'get' ,that.update);
that.addActionRule('get' ,false,'remove' ,that.wait);
that.addActionRule('remove' ,true ,'get' ,that.dontAccept);
that.addActionRule('remove' ,true ,'remove' ,that.dontAccept);
that.addActionRule('remove' ,false,'post' ,that.eliminate);
that.addActionRule('remove' ,false,'put' ,that.eliminate);
that.addActionRule('remove' ,false,'get' ,that.dontAccept);
that.addActionRule('remove' ,false,'remove' ,that.update);
that.addActionRule('allDocs',true ,'allDocs',that.dontAccept);
that.addActionRule('allDocs',false,'allDocs',that.update);
// end adding rules
////////////////////////////////////////////////////////////////////////////
return that;
}());
var doneStatus = function(spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'done';
};
that.canStart = function() {
return false;
};
that.canRestart = function() {
return false;
};
that.isDone = function() {
return true;
};
return that;
};
var failStatus = function(spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'fail';
};
that.canStart = function() {
return false;
};
that.canRestart = function() {
return true;
};
return that;
};
var initialStatus = function(spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'initial';
};
that.canStart = function() {
return true;
};
that.canRestart = function() {
return true;
};
return that;
};
\ No newline at end of file
var jobStatus = function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'job status';
};
that.canStart = function() {};
that.canRestart = function() {};
that.serialized = function() {
return {label:that.getLabel()};
};
that.isWaitStatus = function() {
return false;
};
that.isDone = function() {
return false;
};
return that;
};
var onGoingStatus = function(spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return 'on going';
};
that.canStart = function() {
return false;
};
that.canRestart = function() {
return false;
};
return that;
};
\ No newline at end of file
var waitStatus = function(spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
priv.job_id_array = spec.job_id_array || [];
priv.threshold = 0;
// Methods //
/**
* Returns the label of this status.
* @method getLabel
* @return {string} The label: 'wait'.
*/
that.getLabel = function() {
return 'wait';
};
/**
* Refresh the job id array to wait.
* @method refreshJobIdArray
*/
priv.refreshJobIdArray = function() {
var tmp_job_id_array = [], i;
for (i = 0; i < priv.job_id_array.length; i+= 1) {
if (jobManager.jobIdExists(priv.job_id_array[i])) {
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for the job end before start again.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function(job) {
var i;
for (i = 0; i < priv.job_id_array.length; i+= 1) {
if (priv.job_id_array[i] === job.getId()) {
return;
}
}
priv.job_id_array.push(job.getId());
};
/**
* The status stops to wait for this job.
* @method dontWaitForJob
* @param {object} job The job to stop waiting for.
*/
that.dontWaitForJob = function(job) {
var i, tmp_job_id_array = [];
for (i = 0; i < priv.job_id_array.length; i+= 1) {
if (priv.job_id_array[i] !== job.getId()){
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for some milliseconds.
* @method waitForTime
* @param {number} ms The number of milliseconds
*/
that.waitForTime = function(ms) {
priv.threshold = Date.now() + ms;
};
/**
* The status stops to wait for some time.
* @method stopWaitForTime
*/
that.stopWaitForTime = function() {
priv.threshold = 0;
};
that.canStart = function() {
priv.refreshJobIdArray();
return (priv.job_id_array.length === 0 && Date.now() >= priv.threshold);
};
that.canRestart = function() {
return that.canStart();
};
that.serialized = function() {
return {label:that.getLabel(),
waitfortime:priv.threshold,
waitforjob:priv.job_id_array};
};
/**
* Checks if this status is waitStatus
* @method isWaitStatus
* @return {boolean} true
*/
that.isWaitStatus = function () {
return true;
};
return that;
};
return jioNamespace;
}());
var storage = function(spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
priv.type = spec.type || '';
// Methods //
Object.defineProperty(that,"getType",{
configurable:false,enumerable:false,writable:false,value:
function() {
return priv.type;
}
});
/**
* Execute the command on this storage.
* @method execute
* @param {object} command The command
*/
that.execute = function(command) {
that.success = command.success;
that.error = command.error;
that.retry = command.retry;
that.end = command.end;
if (that.validate(command)) {
command.executeOn(that);
}
};
/**
* Override this function to validate specifications.
* @method isValid
* @return {boolean} true if ok, else false.
*/
that.isValid = function() {
return true;
};
that.validate = function () {
var mess = that.validateState();
if (mess) {
that.error({
status:0,statusText:'Invalid Storage',
error:'invalid_storage',
message:mess,reason:mess
});
return false;
}
return true;
};
/**
* Returns a serialized version of this storage.
* @method serialized
* @return {object} The serialized storage.
*/
that.serialized = function() {
return {type:that.getType()};
};
that.saveDocument = function(command) {
that.error({status:0,statusText:'Unknown storage',
error:'unknown_storage',message:'Unknown Storage'});
};
that.loadDocument = function(command) {
that.saveDocument();
};
that.removeDocument = function(command) {
that.saveDocument();
};
that.getDocumentList = function(command) {
that.saveDocument();
};
/**
* Validate the storage state. It returns a empty string all is ok.
* @method validateState
* @return {string} empty: ok, else error message.
*/
that.validateState = function() {
return '';
};
that.success = function() {};
that.retry = function() {};
that.error = function() {};
that.end = function() {}; // terminate the current job.
priv.newCommand = function (method, spec) {
var o = spec || {};
o.label = method;
return command (o, my);
};
that.addJob = function (method,storage_spec,doc,option,success,error) {
var command_opt = {
options: option,
callbacks:{success:success,error:error}
};
if (doc) {
if (method === 'get') {
command_opt.docid = doc;
} else {
command_opt.doc = doc;
}
}
jobManager.addJob (
job({
storage:my.storage(storage_spec||{}),
command:priv.newCommand(method,command_opt)
}, my)
);
};
return that;
};
var LocalOrCookieStorage =
(function () { var local_cookie_loader_function = function () {
// localorcookiestorage.js
// Creates an object that can store persistent information in localStorage.
// If it is not supported by the browser, it will store in cookies.
// Methods :
// - LocalOrCookieStorage.setItem('name',value);
// Sets an item with its value.
// - LocalOrCookieStorage.getItem('name');
// Returns a copy of the item.
// - LocalOrCookieStorage.deleteItem('name');
// Deletes an item forever.
// - LocalOrCookieStorage.getAll();
// Returns a new object containing all items and their values.
////////////////////////////////////////////////////////////////////////////
// cookies & localStorage
var BrowserStorage = function () {
};
BrowserStorage.prototype = {
getItem: function (name) {
return JSON.parse(localStorage.getItem(name));
},
setItem: function (name,value) {
if (name) {
return localStorage.setItem(name,JSON.stringify(value));
}
},
getAll: function() {
return localStorage;
},
deleteItem: function (name) {
if (name) {
delete localStorage[name];
}
}
};
var CookieStorage = function () {
};
CookieStorage.prototype = {
getItem: function (name) {
var cookies = document.cookie.split(';'), i;
for (i = 0; i < cookies.length; i += 1) {
var x = cookies[i].substr(0, cookies[i].indexOf('=')),
y = cookies[i].substr(cookies[i].indexOf('=')+1);
x = x.replace(/^\s+|\s+$/g,"");
if( x === name ) { return unescape(y); }
}
return null;
},
setItem: function (name,value) {
// function to store into cookies
if (value !== undefined) {
document.cookie = name+'='+JSON.stringify(value)+';domain='+
window.location.hostname+
';path='+window.location.pathname;
return true;
}
return false;
},
getAll: function() {
var retObject = {}, i,
cookies = document.cookie.split(':');
for (i = 0; i < cookies.length; i += 1) {
var x = cookies[i].substr(0, cookies[i].indexOf('=')),
y = cookies[i].substr(cookies[i].indexOf('=')+1);
x = x.replace(/^\s+|\s+$/g,"");
retObject[x] = unescape(y);
}
return retObject;
},
deleteItem: function (name) {
document.cookie = name+'=null;domain='+window.location.hostname+
';path='+window.location.pathname+
';expires=Thu, 01-Jan-1970 00:00:01 GMT';
}
};
// set good localStorage
try {
if (localStorage.getItem) {
return new BrowserStorage();
} else {
return new CookieStorage();
}
}
catch (e) {
return new CookieStorage();
}
// end cookies & localStorages
////////////////////////////////////////////////////////////////////////////
};
if (window.requirejs) {
define ('LocalOrCookieStorage',[], local_cookie_loader_function);
return undefined;
} else {
return local_cookie_loader_function ();
}
}());
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>JIO QUnit/Sinon Test</title>
<link rel="stylesheet" href="../lib/qunit/qunit.css" />
</head>
<body>
<div id="qunit"></div>
<script type="text/javascript" src="../lib/qunit/qunit.js"></script>
<script type="text/javascript" src="../lib/sinon/sinon.js"></script>
<script type="text/javascript" src="../lib/sinon/sinon-qunit.js"></script>
<script type="text/javascript">
<!--
var require = {}; // IE compatibility
//-->
</script>
<script data-main="jiotests" src="../lib/require/require.js"></script>
</body>
</html>
(function () { var thisfun = function(loader) {
var JIO = loader.JIO,
LocalOrCookieStorage = loader.LocalOrCookieStorage,
sjcl = loader.sjcl,
Base64 = loader.Base64,
$ = loader.jQuery;
//// clear jio localstorage
(function () {
var k, storageObject = LocalOrCookieStorage.getAll();
for (k in storageObject) {
var splitk = k.split('/');
if ( splitk[0] === 'jio' ) {
LocalOrCookieStorage.deleteItem(k);
}
}
var d = document.createElement ('div');
d.setAttribute('id','log');
document.querySelector ('body').appendChild(d);
}());
//// end clear jio localstorage
//// Tools
var empty_fun = function (){},
contains = function (array,content) {
var i;
if (typeof array !== 'object') {
return undefined;
}
for (i = 0; i < array.length || 0; i+= 1) {
if (array[i] === content) {
return true;
}
}
return false;
},
base_tick = 30000,
basic_test_function_generator = function(o,res,value,message) {
return function(err,val) {
var jobstatus = (err?'fail':'done');
switch (res) {
case 'status':
err = err || {}; val = err.status;
break;
case 'jobstatus':
val = jobstatus;
break;
case 'value':
val = err || val;
break;
default:
return;
}
deepEqual (val,value,message);
};
},
basic_spy_function = function(o,res,value,message,fun) {
fun = fun || 'f';
o[fun] = basic_test_function_generator(o,res,value,message);
o.t.spy(o,fun);
},
basic_tick_function = function (o) {
var tick, fun, i = 1;
tick = 1000;
fun = fun || 'f';
if (typeof arguments[i] === 'number') {
tick = arguments[i]; i++;
}
if (typeof arguments[i] === 'string') {
fun = arguments[i]; i++;
}
o.clock.tick(tick);
if (!o[fun].calledOnce) {
if (o[fun].called) {
ok(false, 'too much results (o.' + fun +')');
} else {
ok(false, 'no response (o.' + fun +')');
}
}
},
// debug function to show custumized log at the bottom of the page
my_log = function (html_string) {
document.querySelector ('div#log').innerHTML += html_string + '<hr/>';
},
getXML = function (url) {
var tmp = '';
$.ajax({'url':url,async:false,
dataType:'text',success:function(xml){tmp=xml;}});
return tmp;
},
objectifyDocumentArray = function (array) {
var obj = {}, k;
for (k = 0; k < array.length; k += 1) {
obj[array[k].id] = array[k];
}
return obj;
},
addFileToLocalStorage = function (user,appid,file) {
var i, l, found = false, filenamearray,
userarray = LocalOrCookieStorage.getItem('jio/local_user_array') || [];
for (i = 0, l = userarray.length; i < l; i+= 1) {
if (userarray[i] === user) { found = true; }
}
if (!found) {
userarray.push(user);
LocalOrCookieStorage.setItem('jio/local_user_array',userarray);
LocalOrCookieStorage.setItem(
'jio/local_file_name_array/'+user+'/'+appid,[file._id]);
} else {
filenamearray =
LocalOrCookieStorage.getItem(
'jio/local_file_name_array/'+user+'/'+appid) || [];
filenamearray.push(file._id);
LocalOrCookieStorage.setItem(
'jio/local_file_name_array/'+user+'/'+appid,
filenamearray);
LocalOrCookieStorage.setItem(
'jio/local/'+user+'/'+appid+'/'+file._id,
file);
}
LocalOrCookieStorage.setItem(
'jio/local/'+user+'/'+appid+'/'+file._id,
file);
},
removeFileFromLocalStorage = function (user,appid,file) {
var i, l, newarray = [],
filenamearray =
LocalOrCookieStorage.getItem(
'jio/local_file_name_array/'+user+'/'+appid) || [];
for (i = 0, l = filenamearray.length; i < l; i+= 1) {
if (filenamearray[i] !== file._id) {
newarray.push(filenamearray[i]);
}
}
LocalOrCookieStorage.setItem('jio/local_file_name_array/'+user+'/'+appid,
newarray);
LocalOrCookieStorage.deleteItem(
'jio/local/'+user+'/'+appid+'/'+file._id);
},
makeRevsAccordingToRevsInfo = function (revs,revs_info) {
var i, j;
for (i = 0; i < revs.start; i+= 1) {
for (j = 0; j < revs_info.length; j+= 1) {
var id = revs_info[j].rev.split('-'); id.shift(); id = id.join('-');
if (revs.ids[i] === id) {
revs.ids[i] = revs_info[j].rev.split('-')[0];
break;
}
}
}
},
checkRev = function (rev) {
if (typeof rev === 'string') {
if (rev.split('-').length > 1 &&
parseInt(rev.split('-')[0],10) > 0) {
return rev;
}
}
return 'ERROR: not a good revision!';
},
checkConflictRow = function (row) {
var fun;
if (typeof row === 'object') {
if (row.value && typeof row.value._solveConflict === 'function') {
fun = row.value._solveConflict;
row.value._solveConflict = 'function';
}
}
return fun;
},
getHashFromRev = function (rev) {
var id = rev;
if (typeof id === 'string') {
id = id.split('-');
id.shift(); id = id.join('-');
}
return id;
},
revs_infoContains = function (revs_info, rev) {
var i;
if (typeof revs_info !== 'object') {
return undefined;
}
for (i = 0; i < revs_info.length || 0; i+= 1) {
if (revs_info[i].rev && revs_info[i].rev === rev) {
return true;
}
}
return false;
};
//// end tools
//// QUnit Tests ////
module ('Jio Global tests');
test ( "Jio simple methods", function () {
var clock = this.sandbox.useFakeTimers(); clock.tick(base_tick);
// Test Jio simple methods
// It checks if we can create several instance of jio at the same
// time. Checks if they don't overlap informations, if they are
// started and stopped correctly and if they are ready when they
// have to be ready.
var o = {};
o.jio = JIO.newJio();
ok ( o.jio, 'a new jio -> 1');
o.jio2 = JIO.newJio();
ok ( o.jio2, 'another new jio -> 2');
JIO.addStorageType('qunit', empty_fun);
ok ( o.jio2.getId() !== o.jio.getId(), '1 and 2 must be different');
o.jio.stop();
o.jio2.stop();
});
// test ( 'Jio Publish/Sububscribe/Unsubscribe methods', function () {
// // Test the Publisher, Subscriber of a single jio.
// // It is just testing if these function are working correctly.
// // The test publishes an event, waits a little, and check if the
// // event has been received by the callback of the previous
// // subscribe. Then, the test unsubscribe the callback function from
// // the event, and publish the same event. If it receives the event,
// // the unsubscribe method is not working correctly.
// var o = {};
// o.jio = JIO.newJio();
// var spy1 = this.spy();
// // Subscribe the pubsub_test event.
// o.callback = o.jio.subscribe('pubsub_test',spy1);
// // And publish the event.
// o.jio.publish('pubsub_test');
// ok (spy1.calledOnce, 'subscribing & publishing, event called once');
// o.jio.unsubscribe('pubsub_test',spy1);
// o.jio.publish('pubsub_test');
// ok (spy1.calledOnce, 'unsubscribing, same event not called twice');
// o.jio.stop();
// });
module ( 'Jio Dummy Storages' );
test ('All tests', function () {
// Tests all dummy storages from jio.dummystorages.js
// It is simple tests, but they will be used by replicate storage later
// for sync operation.
var o = {}; o.t = this; o.clock = o.t.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
// All Ok Dummy Storage
o.jio = JIO.newJio({'type':'dummyallok'});
// save
o.spy(o,'value',{ok:true,id:'file'},'dummyallok saving');
o.jio.put({_id:'file',content:'content'},o.f);
o.tick(o);
// load
o.spy(o,'value',{_id:'file',content:'content',_last_modified:15000,
_creation_date:10000},'dummyallok loading');
o.jio.get('file',o.f);
o.tick(o);
// remove
o.spy(o,'value',{ok:true,id:"file"},'dummyallok removing');
o.jio.remove({_id:'file'},o.f);
o.tick(o);
// get list
o.spy (o,'value',{
total_rows:2,
rows:[{
id:'file',key:'file',
value:{
content:'filecontent',
_last_modified:15000,
_creation_date:10000
}
},{
id:'memo',key:'memo',
value:{
content:'memocontent',
_last_modified:25000,
_creation_date:20000
}
}]
},'dummyallok getting list');
o.jio.allDocs({metadata_only:false},o.f);
o.tick(o);
o.jio.stop();
o.jio = JIO.newJio({'type':'dummyallok'});
// save
o.spy(o,'value',{ok:true,id:'file'},'dummyallok saving1','f');
o.spy(o,'value',{ok:true,id:'file2'},'dummyallok saving2','f2');
o.spy(o,'value',{ok:true,id:'file3'},'dummyallok saving3','f3');
o.jio.put({_id:'file',content:'content'},o.f);
o.jio.put({_id:'file2',content:'content2'},o.f2);
o.jio.put({_id:'file3',content:'content3'},o.f3);
o.tick(o, 1000, 'f');
o.tick(o, 'f2');
o.tick(o, 'f3');
o.jio.stop();
// All Fail Dummy Storage
o.jio = JIO.newJio({'type':'dummyallfail'});
// save
o.spy (o,'status',0,'dummyallfail saving');
o.jio.put({_id:'file',content:'content'},o.f);
o.tick(o);
// load
o.spy (o,'status',0,'dummyallfail loading');
o.jio.get('file',o.f);
o.tick(o);
// remove
o.spy (o,'status',0,'dummyallfail removing');
o.jio.remove({_id:'file'},o.f);
o.tick(o);
// get list
o.spy (o,'status',0,'dummyallfail getting list');
o.jio.allDocs(o.f);
o.tick(o);
o.jio.stop();
// All Not Found Dummy Storage
o.jio = JIO.newJio({'type':'dummyallnotfound'});
// save
o.spy(o,'value',{ok:true,id:'file'},'dummyallnotfound saving');
o.jio.put({_id:'file',content:'content'},o.f);
o.tick(o);
// load
o.spy(o,'status',404,'dummyallnotfound loading')
o.jio.get('file',o.f);
o.tick(o);
// remove
o.spy(o,'value',{ok:true,id:'file'},'dummyallnotfound removing');
o.jio.remove({_id:'file'},o.f);
o.tick(o);
// get list
o.spy(o,'status',404,'dummyallnotfound getting list');
o.jio.allDocs (o.f);
o.tick(o);
o.jio.stop();
});
module ( 'Jio Job Managing' );
test ('Simple Job Elimination', function () {
var clock = this.sandbox.useFakeTimers(); clock.tick(base_tick);
var o = {}, id = 0;
o.f1 = this.spy(); o.f2 = this.spy();
o.jio = JIO.newJio({type:'dummyallok',applicationname:'jiotests'});
id = o.jio.getId();
o.jio.put({_id:'file',content:'content'},
{max_retry:1},o.f1);
ok(LocalOrCookieStorage.getItem('jio/job_array/'+id)[0],
'job creation');
o.jio.remove({_id:'file'},{max_retry:1},o.f2);
o.tmp = LocalOrCookieStorage.getItem('jio/job_array/'+id)[0];
deepEqual(o.tmp.command.label,'remove','job elimination');
o.jio.stop();
});
test ('Simple Job Replacement', function () {
// Test if the second job write over the first one
var o = {};
o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.id = 0;
o.f1 = function (err,val) {
if (err) {
o.err = err;
} else {
o.err = {status:'done'};
}
};
this.spy(o,'f1');
o.f2 = this.spy();
o.jio = JIO.newJio({type:'dummyallok',applicationname:'jiotests'});
o.id = o.jio.getId();
o.jio.put({_id:'file',content:'content'},o.f1);
o.clock.tick(10);
o.jio.put({_id:'file',content:'content'},o.f2);
deepEqual(LocalOrCookieStorage.getItem(
'jio/job_array/'+o.id)[0].date,base_tick + 10,
'The first job date have to be equal to the second job date.');
o.clock.tick(1000);
deepEqual([o.f1.calledOnce,o.err.status],[true,12],
'callback for the first save request -> result fail');
ok(o.f2.calledOnce,'second callback is called once');
o.jio.stop();
o.jio = JIO.newJio({type:'dummyallok',applicationname:'jiotests'});
o.ok1 = 0;
o.jio.get('file1',function (err,val) {
deepEqual (err || val,
{_id:'file1',content:'content',
_creation_date:10000,_last_modified:15000},
'First load');
o.ok1 ++;
});
o.ok2 = 0;
o.jio.get('file2',function (err,val) {
deepEqual (err || val,
{_id:'file2',content:'content',
_creation_date:10000,_last_modified:15000},
'Second load must not replace the first one');
o.ok2 ++;
});
o.clock.tick(1000);
if (o.ok1 !== 1) {
ok (false,'no response / too much response');
}
if (o.ok2 !== 1) {
ok (false,'no response / too much response');
}
o.jio.stop();
});
test ('Simple Job Waiting', function () {
// Test if the second job doesn't erase the first on going one
var o = {};
o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.id = 0;
o.f = function (err,val) {
deepEqual(err || val,{ok:true,id:'file'},'job 1 result');
};
o.f3 = o.f; this.spy(o,'f3');
o.f4 = o.f; this.spy(o,'f4');
o.checkCallback = function (fun_name,message) {
if (!o[fun_name].calledOnce) {
if (o[fun_name].called) {
ok(false, 'too much response');
} else {
ok(false, 'no response');
}
} else {
ok(true,message);
}
};
o.jio = JIO.newJio({type:'dummyallok',applicationname:'jiotests'});
o.id = o.jio.getId();
o.jio.put({_id:'file',content:'content'},o.f3);
o.clock.tick(200);
o.jio.put({_id:'file',content:'content1'},o.f4);
o.tmp0 = LocalOrCookieStorage.getItem('jio/job_array/'+o.id)[0];
o.tmp1 = LocalOrCookieStorage.getItem('jio/job_array/'+o.id)[1];
ok(o.tmp0 && o.tmp0.id === 1,'job 1 exists');
deepEqual(o.tmp0.status.label,'on going','job 1 is on going');
ok(o.tmp1 && o.tmp1.id === 2,'job 2 exists');
deepEqual(o.tmp1.status.label,'wait','job 2 waiting');
deepEqual(o.tmp1.status.waitforjob,[1],
'job 2 must wait for the first to end');
o.clock.tick(1000);
o.checkCallback('f3','first request passed');
o.checkCallback('f4','restore waiting job');
o.jio.stop();
});
test ('Simple Time Waiting' , function () {
// Test if the job that have fail wait until a certain moment to restart.
// It will use the dummyall3tries, which will work after the 3rd try.
var o = {}, clock = this.sandbox.useFakeTimers(), id = 0;
clock.tick(base_tick);
o.f = function (err,val) {
if (err) {
o.res = err;
} else {
o.res = val;
}
};
this.spy(o,'f');
o.jio = JIO.newJio({type:'dummyall3tries',applicationname:'jiotests'});
o.jio.put({_id:'file',content:'content'},{max_retry:3},o.f);
clock.tick(10000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false,'callback called too much times.');
} else {
ok(false,'no response.');
}
}
deepEqual(o.res,{ok:true,id:'file'},'job done.');
o.jio.stop();
});
module ( 'Jio Restore');
test ('Restore old Jio', function() {
var o = {};
o.clock = this.sandbox.useFakeTimers();
o.f = function() {
ok(false,'must never be called!');
};
this.spy(o,'f');
o.jio = JIO.newJio({type:'dummyall3tries',applicationname:'jiotests'});
o.id = o.jio.getId();
ok(true,'create jio, id = ' + o.id);
o.jio.put({_id:'file',content:'content'},{max_retry:3},o.f);
o.clock.tick(1000);
o.jio.close();
o.jio = JIO.newJio({type:'dummyallok',applicationname:'jiotests'});
o.clock.tick(11000); // 10 sec
deepEqual(LocalOrCookieStorage.getItem('jio/job_array/'+o.id),null,
'job array list must be empty');
o.tmp1 = LocalOrCookieStorage.getItem('jio/job_array/'+o.jio.getId());
if (o.tmp1.length > 0) {
deepEqual([o.tmp1[0].command.label,o.tmp1[0].command.doc._id,
o.tmp1[0].command.doc.content],
['put','file','content'],
'job which id is id = ' +o.jio.getId()+', restored the jio');
} else {
ok (false, 'The recovered job must exists');
}
o.jio.stop();
});
module ( 'Jio LocalStorage' );
test ('Document save', function () {
// Test if LocalStorage can save documents.
// We launch a saving to localstorage and we check if the file is
// realy saved. Then save again and check if
var o = {}; o.t = this; o.clock = o.t.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.tick = function (o, tick, value, fun) {
basic_tick_function(o,tick,fun);
o.tmp =
LocalOrCookieStorage.getItem ('jio/local/MrSaveName/jiotests/file');
if (o.tmp) {
o.tmp.lmcd = (o.tmp._last_modified === o.tmp._creation_date);
delete o.tmp._last_modified;
delete o.tmp._creation_date;
deepEqual (o.tmp,{_id:'file',content:'content',lmcd:value},
'check saved document');
} else {
ok (false, 'document is not saved!');
}
};
o.jio = JIO.newJio({type:'local',username:'MrSaveName',
applicationname:'jiotests'});
// save and check document existence
o.spy (o,'value',{ok:true,id:'file'},'saving document');
o.jio.put({_id:'file',content:'content'},o.f);
o.tick(o,null,true);
o.spy (o,'value',{ok:true,id:'file'},'saving document');
o.jio.put({_id:'file',content:'content'},o.f);
o.tick(o,null,false);
o.jio.stop();
});
test ('Document load', function () {
// Test if LocalStorage can load documents.
// We launch a loading from localstorage and we check if the file is
// realy loaded.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.jio = JIO.newJio({type:'local',username:'MrLoadName',
applicationname:'jiotests'});
// save and check document existence
o.doc = {_id:'file',content:'content',
_last_modified:1234,_creation_date:1000};
o.spy(o,'status',404,'loading document failure');
o.jio.get('file',o.f);
o.tick(o);
addFileToLocalStorage('MrLoadName','jiotests',o.doc);
o.spy(o,'value',o.doc,'loading document success');
o.jio.get('file',o.f);
o.tick(o);
o.jio.stop();
});
test ('Get document list', function () {
// Test if LocalStorage can get a list of documents.
// We create 2 documents inside localStorage to check them.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (value){
o.f = function (err,val) {
if (val) {
deepEqual (objectifyDocumentArray(val.rows),
objectifyDocumentArray(value),'getting list');
} else {
deepEqual (err,value,'getting list');
}
};
o.t.spy(o,'f');
o.jio.allDocs(o.f);
o.clock.tick(1000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'local',username:'MrListName',
applicationname:'jiotests'});
o.doc1 = {_id:'file',content:'content',
_last_modified:1,_creation_date:0};
o.doc2 = {_id:'memo',content:'test',
_last_modified:5,_creation_date:2};
addFileToLocalStorage ('MrListName','jiotests',o.doc1);
addFileToLocalStorage ('MrListName','jiotests',o.doc2);
o.mytest ([{
id:o.doc2._id,key:o.doc2._id,
value:{
_creation_date:o.doc2._creation_date,
_last_modified:o.doc2._last_modified
}
},{
id:o.doc1._id,key:o.doc1._id,
value:{
_last_modified:o.doc1._last_modified,
_creation_date:o.doc1._creation_date
}
}]);
o.jio.stop();
});
test ('Document remove', function () {
// Test if LocalStorage can remove documents.
// We launch a remove from localstorage and we check if the file is
// realy removed.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.tick = function () {
basic_tick_function.apply(basic_tick_function,arguments);
// check if the file is still there
o.tmp = LocalOrCookieStorage.getItem (
'jio/local/MrRemoveName/jiotests/file');
ok (!o.tmp, 'check no content');
};
o.jio = JIO.newJio({type:'local',username:'MrRemoveName',
applicationname:'jiotests'});
// test removing a file
o.spy (o,'value',{ok:true,id:'file'},'removing document');
addFileToLocalStorage ('MrRemoveName','jiotests',{_id:'file'});
o.jio.remove({_id:'file'},o.f);
o.tick (o);
o.jio.stop();
});
module ('Jio DAVStorage');
test ('Document load', function () {
// Test if DavStorage can load documents.
var o = {};
o.davload = getXML('responsexml/davload'),
o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.t = this;
o.mytest = function (message,doc,errprop,errget) {
var server = o.t.sandbox.useFakeServer();
server.respondWith (
"PROPFIND",
/https:\/\/ca-davstorage:8080\/davload\/jiotests\/file(\?.*|$)/,
[errprop,{'Content-Type':'text/xml; charset="utf-8"'},
o.davload]);
server.respondWith (
"GET",
/https:\/\/ca-davstorage:8080\/davload\/jiotests\/file(\?.*|$)/,
[errget,{},'content']);
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,doc,message);
};
o.t.spy(o,'f');
o.jio.get('file',{max_retry:1},o.f);
o.clock.tick(1000);
server.respond();
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'dav',username:'davload',
password:'checkpwd',
url:'https://ca-davstorage:8080',
applicationname:'jiotests'});
// note: http errno:
// 200 OK
// 201 Created
// 204 No Content
// 207 Multi Status
// 403 Forbidden
// 404 Not Found
// load an inexistant document.
o.mytest ('load inexistant document',404,404,404);
// load a document.
o.mytest ('load document',{_id:'file',content:'content',
_last_modified:1335953199000,
_creation_date:1335953202000},207,200);
o.jio.stop();
});
test ('Document save', function () {
// Test if DavStorage can save documents.
var o = {};
o.davsave = getXML('responsexml/davsave');
o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.t = this;
o.mytest = function (message,value,errnoput,errnoprop) {
var server = o.t.sandbox.useFakeServer();
server.respondWith (
// lastmodified = 7000, creationdate = 5000
"PROPFIND",
/https:\/\/ca-davstorage:8080\/davsave\/jiotests\/file(\?.*|$)/,
[errnoprop,{'Content-Type':'text/xml; charset="utf-8"'},
o.davsave]);
server.respondWith (
"PUT",
/https:\/\/ca-davstorage:8080\/davsave\/jiotests\/file(\?.*|$)/,
[errnoput, {'Content-Type':'x-www-form-urlencoded'},
'content']);
server.respondWith (
"GET",
/https:\/\/ca-davstorage:8080\/davsave\/jiotests\/file(\?.*|$)/,
[errnoprop===207?200:errnoprop,{},'content']);
// server.respondWith ("MKCOL","https://ca-davstorage:8080/dav",
// [200,{},'']);
// server.respondWith ("MKCOL","https://ca-davstorage:8080/dav/davsave",
// [200,{},'']);
// server.respondWith ("MKCOL",
// "https://ca-davstorage:8080/dav/davsave/jiotests",
// [200,{},'']);
o.f = basic_test_function_generator(o,'value',value,message);
o.t.spy(o,'f');
o.jio.put({_id:'file',content:'content'},o.f);
o.clock.tick(1000);
server.respond();
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'dav',username:'davsave',
password:'checkpwd',
url:'https://ca-davstorage:8080',
applicationname:'jiotests'});
// note: http errno:
// 200 OK
// 201 Created
// 204 No Content
// 207 Multi Status
// 403 Forbidden
// 404 Not Found
// // the path does not exist, we want to create it, and save the file.
// mytest('create path if not exists, and create document',
// true,201,404);
// the document does not exist, we want to create it
o.mytest('create document',{ok:true,id:'file'},201,404);
o.clock.tick(8000);
// the document already exists, we want to overwrite it
o.mytest('overwrite document',{ok:true,id:'file'},204,207);
o.jio.stop();
});
test ('Get Document List', function () {
// Test if DavStorage can get a list a document.
var o = {};
o.davlist = getXML('responsexml/davlist');
o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.t = this;
o.mytest = function (message,metadata_only,value,errnoprop) {
var server = o.t.sandbox.useFakeServer();
server.respondWith (
"PROPFIND",
/https:\/\/ca-davstorage:8080\/davlist\/jiotests\/(\?.*|$)/,
[errnoprop,{'Content-Type':'text/xml; charset="utf-8"'},
o.davlist]);
server.respondWith (
"GET",
/https:\/\/ca-davstorage:8080\/davlist\/jiotests\/file(\?.*|$)/,
[200,{},'content']);
server.respondWith (
"GET",
/https:\/\/ca-davstorage:8080\/davlist\/jiotests\/memo(\?.*|$)/,
[200,{},'content2']);
o.f = function (err,val) {
if (err) {
result = undefined;
} else {
deepEqual (objectifyDocumentArray(val.rows),
objectifyDocumentArray(value),message);
return;
}
deepEqual (result, value, message);
};
o.t.spy(o,'f');
o.jio.allDocs({metadata_only:metadata_only},o.f);
o.clock.tick(1000);
server.respond();
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'dav',username:'davlist',
password:'checkpwd',
url:'https://ca-davstorage:8080',
applicationname:'jiotests'});
o.mytest('fail to get list',true,undefined,404);
o.mytest('getting list',true,[{
id:'file',key:'file',
value:{
_creation_date:1335962911000,
_last_modified:1335962907000
}
},{
id:'memo',key:'memo',
value:{
_creation_date:1335894073000,
_last_modified:1335955713000
}
}],207);
o.mytest('getting list',false,[{
id:'file',key:'file',
value:{
content:'content',
_creation_date:1335962911000,
_last_modified:1335962907000
}
},{
id:'memo',key:'memo',
value:{
content:'content2',
_creation_date:1335894073000,
_last_modified:1335955713000
}
}],207);
o.jio.stop();
});
test ('Remove document', function () {
// Test if DavStorage can remove documents.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (message,value,errnodel) {
var server = o.t.sandbox.useFakeServer();
server.respondWith (
"DELETE",
/https:\/\/ca-davstorage:8080\/davremove\/jiotests\/file(\?.*|$)/,
[errnodel,{},'']);
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,value,message);
};
o.t.spy(o,'f');
o.jio.remove({_id:'file'},o.f);
o.clock.tick(1000);
server.respond();
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'dav',username:'davremove',
password:'checkpwd',
url:'https://ca-davstorage:8080',
applicationname:'jiotests'});
o.mytest('remove document',{ok:true,id:'file'},204);
o.mytest('remove an already removed document',404,404);
o.jio.stop();
});
module ('Jio ReplicateStorage');
test ('Document load', function () {
// Test if ReplicateStorage can load several documents.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (message,doc,doc2) {
o.f = function (err,val) {
var gooddoc = doc;
if (val) {
if (doc2 && val.content === doc2.content) {
gooddoc = doc2;
}
}
deepEqual (err || val,gooddoc,message);
};
o.t.spy(o,'f');
o.jio.get('file',{max_retry:3},o.f);
o.clock.tick(10000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyallok',username:'1'},
{type:'dummyallok',username:'2'}]});
o.mytest('DummyStorageAllOK,OK: load same file',{
_id:'file',content:'content',
_last_modified:15000,
_creation_date:10000
});
o.jio.stop();
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyall3tries'},
{type:'dummyallok'}]});
o.mytest('DummyStorageAllOK,3tries: load 2 different files',
{
_id:'file',content:'content',
_last_modified:15000,_creation_date:10000
},{
_id:'file',content:'content file',
_last_modified:17000,_creation_date:11000
});
o.jio.stop();
});
test ('Document save', function () {
// Test if ReplicateStorage can save several documents.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (message,value) {
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,value,message);
};
o.t.spy(o,'f');
o.jio.put({_id:'file',content:'content'},{max_retry:3},o.f);
o.clock.tick(500);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyallok',username:'1'},
{type:'dummyallok',username:'2'}]});
o.mytest('DummyStorageAllOK,OK: save a file.',{ok:true,id:'file'});
o.jio.stop();
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyall3tries',username:'1'},
{type:'dummyallok',username:'2'}]});
o.mytest('DummyStorageAll3Tries,OK: save a file.',{ok:true,id:'file'});
o.jio.stop();
});
test ('Get Document List', function () {
// Test if ReplicateStorage can get several list.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (message,value) {
o.f = function (err,val) {
deepEqual (err || objectifyDocumentArray(val.rows),
objectifyDocumentArray(value),message);
};
o.t.spy(o,'f');
o.jio.allDocs({max_retry:3},o.f);
o.clock.tick(10000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyall3tries',username:'1'},
{type:'dummyallok',username:'2'}]});
o.doc1 = {id:'file',key:'file',value:{
_last_modified:15000,_creation_date:10000}};
o.doc2 = {id:'memo',key:'memo',value:{
_last_modified:25000,_creation_date:20000}};
o.mytest('DummyStorageAllOK,3tries: get document list.',
[o.doc1,o.doc2]);
o.jio.stop();
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyall3tries',username:'3'},
{type:'dummyall3tries',username:'4'}]});
o.mytest('DummyStorageAll3tries,3tries: get document list.',
[o.doc1,o.doc2]);
o.jio.stop();
});
test ('Remove document', function () {
// Test if ReplicateStorage can remove several documents.
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.mytest = function (message,value) {
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,value,message);
};
o.t.spy(o,'f');
o.jio.remove({_id:'file'},{max_retry:3},o.f);
o.clock.tick(10000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'replicate',storagelist:[
{type:'dummyallok',username:'1'},
{type:'dummyall3tries',username:'2'}]});
o.mytest('DummyStorageAllOK,3tries: remove document.',{ok:true,id:'file'});
o.jio.stop();
});
module ('Jio IndexedStorage');
test ('Document load', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.jio = JIO.newJio({type:'indexed',storage:{type:'dummyall3tries'}});
// loading must take long time with dummyall3tries
o.f = this.spy();
o.jio.get('memo',{max_retry:3,metadata_only:true},o.f);
o.clock.tick(1000);
ok(!o.f.called,'Callback must not be called');
// wait long time too retreive list
o.clock.tick(1000);
// now we can test if the document metadata are loaded faster.
o.doc = {_id:'memo',_last_modified:25000,_creation_date:20000};
o.f2 = function (err,val) {
deepEqual (err||val,o.doc,'Document metadata retrieved');
};
this.spy(o,'f2');
o.jio.get('memo',{max_retry:3,metadata_only:true},o.f2);
o.clock.tick(1000);
if (!o.f2.calledOnce) {
if (o.f2.called) {
ok (false, 'too much results');
} else {
ok (false, 'no response');
}
}
// test a simple document loading
o.doc2 = {_id:'file',_last_modified:17000,
_creation_date:11000,content:'content file'};
o.f3 = function (err,val) {
deepEqual (err||val,o.doc2,'Simple document loading');
};
this.spy(o,'f3');
o.jio.get('file',{max_retry:3},o.f3);
o.clock.tick(2000);
if (!o.f3.calledOnce) {
ok (false, 'no response / too much results');
}
o.jio.stop();
});
test ('Document save', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.jio = JIO.newJio({type:'indexed',
storage:{type:'dummyall3tries',
username:'indexsave'}});
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,{ok:true,id:'file'},'document save');
};
this.spy(o,'f');
o.jio.put({_id:'file',content:'content'},{max_retry:3},o.f);
o.clock.tick(2000);
if (!o.f.calledOnce){
ok (false, 'no response / too much results');
}
o.jio.stop();
});
test ('Get document list', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.jio = JIO.newJio({type:'indexed',
storage:{type:'dummyall3tries',
username:'indexgetlist'}});
o.doc1 = {id:'file',key:'file',value:{
_last_modified:15000,_creation_date:10000}};
o.doc2 = {id:'memo',key:'memo',value:{
_last_modified:25000,_creation_date:20000}};
// getting list must take long time with dummyall3tries
o.f = this.spy();
o.jio.allDocs({max_retry:3},o.f);
o.clock.tick(1000);
ok(!o.f.called,'Callback must not be called');
// wail long time too retreive list
o.clock.tick(1000);
// now we can test if the document list is loaded faster
o.f2 = function (err,val) {
deepEqual (err || objectifyDocumentArray(val.rows),
objectifyDocumentArray([o.doc1,o.doc2]),'get document list');
};
this.spy(o,'f2');
o.jio.allDocs({max_retry:3},o.f2);
o.clock.tick(1000)
if (!o.f2.calledOnce) {
ok (false, 'no response / too much results');
}
});
test ('Remove document', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers();
o.clock.tick(base_tick);
o.secondstorage = {type:'dummyall3tries',username:'indexremove'}
o.storage_file_object_name = 'jio/indexed_file_object/'+
JSON.stringify (o.secondstorage);
o.jio = JIO.newJio({type:'indexed',storage:o.secondstorage});
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,{ok:true,id:'file'},'document remove');
};
this.spy(o,'f');
o.jio.remove({_id:'file'},{max_retry:3},o.f);
o.clock.tick(2000);
if (!o.f.calledOnce){
ok (false, 'no response / too much results');
}
o.tmp = LocalOrCookieStorage.getItem(o.storage_file_object_name) || {};
ok (!o.tmp.file,'File does not exists anymore');
o.jio.stop();
});
module ('Jio CryptedStorage');
test ('Document save' , function () {
var o = {}, clock = this.sandbox.useFakeTimers();
clock.tick(base_tick);
o.jio=JIO.newJio({type:'crypt',
username:'cryptsave',
password:'mypwd',
storage:{type:'local',
username:'cryptsavelocal',
applicationname:'jiotests'}});
o.f = function (err,val) {
if (err) {
err = err.status;
}
deepEqual (err || val,{ok:true,id:'testsave'},'save ok');
};
this.spy(o,'f');
o.jio.put({_id:'testsave',content:'contentoftest'},o.f);
clock.tick(1000);
if (!o.f.calledOnce) {
ok (false, 'no response / too much results');
}
// encrypt 'testsave' with 'cryptsave:mypwd' password
o.tmp = LocalOrCookieStorage.getItem( // '/' = '%2F'
'jio/local/cryptsavelocal/jiotests/rZx5PJxttlf9QpZER%2F5x354bfX54QFa1');
if (o.tmp) {
delete o.tmp._last_modified;
delete o.tmp._creation_date;
}
deepEqual (o.tmp,
{_id:'rZx5PJxttlf9QpZER/5x354bfX54QFa1',
content:'upZkPIpitF3QMT/DU5jM3gP0SEbwo1n81rMOfLE'},
'Check if the document is realy encrypted');
o.jio.stop();
});
test ('Document load' , function () {
var o = {}, clock = this.sandbox.useFakeTimers();
clock.tick(base_tick);
o.jio=JIO.newJio({type:'crypt',
username:'cryptload',
password:'mypwd',
storage:{type:'local',
username:'cryptloadlocal',
applicationname:'jiotests'}});
o.f = function (err,val) {
deepEqual (err || val,{
_id:'testload',content:'contentoftest',
_last_modified:500,_creation_date:500},'load ok');
};
this.spy(o,'f');
// encrypt 'testload' with 'cryptload:mypwd' password
// and 'contentoftest' with 'cryptload:mypwd'
o.doc = {
_id:'hiG4H80pwkXCCrlLl1X0BD0BfWLZwDUX',
content:'kSulH8Qo105dSKHcY2hEBXWXC9b+3PCEFSm1k7k',
_last_modified:500,_creation_date:500};
addFileToLocalStorage('cryptloadlocal','jiotests',o.doc);
o.jio.get('testload',o.f);
clock.tick(1000);
if (!o.f.calledOnce) {
ok (false, 'no response / too much results');
}
o.jio.stop();
});
test ('Get Document List', function () {
var o = {}, clock = this.sandbox.useFakeTimers();
clock.tick(base_tick);
o.jio=JIO.newJio({type:'crypt',
username:'cryptgetlist',
password:'mypwd',
storage:{type:'local',
username:'cryptgetlistlocal',
applicationname:'jiotests'}});
o.f = function (err,val) {
deepEqual (err || objectifyDocumentArray(val.rows),
objectifyDocumentArray(o.doc_list),'Getting list');
};
o.tick = function (tick) {
clock.tick (tick || 1000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok (false, 'too much results');
} else {
ok (false, 'no response');
}
}
};
this.spy(o,'f');
o.doc_list = [{
id:'testgetlist1',key:'testgetlist1',value:{
_last_modified:500,_creation_date:200}
},{
id:'testgetlist2',key:'testgetlist2',value:{
_last_modified:300,_creation_date:300}
}];
o.doc_encrypt_list = [
{_id:'541eX0WTMDw7rqIP7Ofxd1nXlPOtejxGnwOzMw',
content:'/4dBPUdmLolLfUaDxPPrhjRPdA',
_last_modified:500,_creation_date:200},
{_id:'541eX0WTMDw7rqIMyJ5tx4YHWSyxJ5UjYvmtqw',
content:'/4FBALhweuyjxxD53eFQDSm4VA',
_last_modified:300,_creation_date:300}
];
// encrypt with 'cryptgetlist:mypwd' as password
LocalOrCookieStorage.setItem(
'jio/local_file_name_array/cryptgetlistlocal/jiotests',
[o.doc_encrypt_list[0]._id,o.doc_encrypt_list[1]._id]);
LocalOrCookieStorage.setItem(
'jio/local/cryptgetlistlocal/jiotests/'+o.doc_encrypt_list[0]._id,
o.doc_encrypt_list[0]);
LocalOrCookieStorage.setItem(
'jio/local/cryptgetlistlocal/jiotests/'+o.doc_encrypt_list[1]._id,
o.doc_encrypt_list[1]);
o.jio.allDocs(o.f);
o.tick(10000);
o.jio.stop();
});
test ('Remove document', function () {
var o = {}, clock = this.sandbox.useFakeTimers();
clock.tick(base_tick);
o.jio=JIO.newJio({type:'crypt',
username:'cryptremove',
password:'mypwd',
storage:{type:'local',
username:'cryptremovelocal',
applicationname:'jiotests'}});
o.f = function (err,val) {
deepEqual (err || val,{ok:true,id:'file'},'Document remove');
};
this.spy(o,'f');
// encrypt with 'cryptremove:mypwd' as password
o.doc = {_id:'JqCLTjyxQqO9jwfxD/lyfGIX+qA',
content:'LKaLZopWgML6IxERqoJ2mUyyO',
_last_modified:500,_creation_date:500};
o.jio.remove({_id:'file'},o.f);
clock.tick(1000);
if (!o.f.calledOnce){
ok (false, 'no response / too much results');
}
o.jio.stop();
});
module ('Jio ConflictManagerStorage');
test ('Simple methods', function () {
// Try all the simple methods like saving, loading, removing a document and
// getting a list of document without testing conflicts
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick(base_tick);
o.spy = function(value,message) {
o.f = function(err,val) {
deepEqual (err || val,value,message);
};
o.t.spy(o,'f');
};
o.tick = function (tick) {
o.clock.tick(tick || 1000);
if (!o.f.calledOnce) {
if (o.f.called) {
ok(false, 'too much results');
} else {
ok(false, 'no response');
}
}
};
o.jio = JIO.newJio({type:'conflictmanager',
username:'methods',
storage:{type:'local',
username:'conflictmethods',
applicationname:'jiotests'}});
// PUT
o.spy({ok:true,id:'file.doc',rev:'1'},'saving "file.doc".');
o.jio.put({_id:'file.doc',content:'content1'},function (err,val) {
if (val) {
o.rev1 = val.rev;
val.rev = val.rev.split('-')[0];
}
o.f (err,val);
});
o.tick();
// PUT with options
o.spy({ok:true,id:'file2.doc',rev:'1',
conflicts:{total_rows:0,rows:[]},
revisions:{start:1,ids:['1']},
revs_info:[{rev:'1',status:'available'}]},
'saving "file2.doc".');
o.jio.put({_id:'file2.doc',content:'yes'},
{revs:true,revs_info:true,conflicts:true},
function (err,val) {
if (val) {
o.rev2 = val.rev;
val.rev = val.rev.split('-')[0];
if (val.revs_info) {
if (val.revisions) {
makeRevsAccordingToRevsInfo(
val.revisions,val.revs_info);
}
val.revs_info[0].rev =
val.revs_info[0].rev.split('-')[0];
}
}
o.f (err,val);
});
o.tick();
// GET
o.get_callback = function (err,val) {
if (val) {
val._rev = (val._rev?val._rev.split('-')[0]:'/');
val._creation_date = (val._creation_date?true:undefined);
val._last_modified = (val._last_modified?true:undefined);
}
o.f(err,val);
};
o.spy({_id:'file.doc',content:'content1',_rev:'1',
_creation_date:true,_last_modified:true},'loading "file.doc".');
o.jio.get('file.doc',o.get_callback);
o.tick();
// GET with options
o.get_callback = function (err,val) {
if (val) {
val._rev = (val._rev?val._rev.split('-')[0]:'/');
val._creation_date = (val._creation_date?true:undefined);
val._last_modified = (val._last_modified?true:undefined);
if (val._revs_info) {
if (val._revisions) {
makeRevsAccordingToRevsInfo(
val._revisions,val._revs_info);
}
val._revs_info[0].rev =
val._revs_info[0].rev.split('-')[0];
}
}
o.f(err,val);
};
o.spy({_id:'file2.doc',content:'yes',_rev:'1',
_creation_date:true,_last_modified:true,
_conflicts:{total_rows:0,rows:[]},
_revisions:{start:1,ids:['1']},
_revs_info:[{rev:'1',status:'available'}]},
'loading "file2.doc".');
o.jio.get('file2.doc',{revs:true,revs_info:true,conflicts:true},
o.get_callback);
o.tick();
// allDocs
o.spy({total_rows:2,rows:[{
id:'file.doc',key:'file.doc',
value:{_rev:'1',_creation_date:true,_last_modified:true}
},{
id:'file2.doc',key:'file2.doc',
value:{_rev:'1',_creation_date:true,_last_modified:true}
}]},'getting list.');
o.jio.allDocs(function (err,val) {
if (val) {
var i;
for (i = 0; i < val.total_rows; i+= 1) {
val.rows[i].value._creation_date =
val.rows[i].value._creation_date?
true:undefined;
val.rows[i].value._last_modified =
val.rows[i].value._last_modified?
true:undefined;
val.rows[i].value._rev = val.rows[i].value._rev.split('-')[0];
}
// because the result can be disordered
if (val.total_rows === 2 && val.rows[0].id === 'file2.doc') {
var tmp = val.rows[0];
val.rows[0] = val.rows[1];
val.rows[1] = tmp;
}
}
o.f(err,val);
});
o.tick();
// remove
o.spy({ok:true,id:'file.doc',rev:'2'},
'removing "file.doc"');
o.jio.remove({_id:'file.doc'},{rev:o.rev1},function (err,val) {
if (val) {
val.rev = val.rev?val.rev.split('-')[0]:undefined;
}
o.f(err,val);
});
o.tick();
// remove with options
o.spy({
ok:true,id:'file2.doc',rev:'2',
conflicts:{total_rows:0,rows:[]},
revisions:{start:2,ids:['2',getHashFromRev(o.rev2)]},
revs_info:[{rev:'2',status:'deleted'}]
},'removing "file2.doc"');
o.jio.remove(
{_id:'file2.doc'},
{rev:o.rev2,conflicts:true,revs:true,revs_info:true},
function (err,val) {
if (val) {
val.rev = val.rev?val.rev.split('-')[0]:undefined;
if (val.revs_info) {
if (val.revisions) {
makeRevsAccordingToRevsInfo(
val.revisions,val.revs_info);
}
val.revs_info[0].rev =
val.revs_info[0].rev.split('-')[0];
}
}
o.f(err,val);
});
o.tick();
o.spy(404,'loading document fail.');
o.jio.get('file.doc',function (err,val) {
if (err) {
err = err.status;
}
o.f(err,val);
});
o.tick();
o.jio.stop();
});
test ('Revision Conflict', function() {
// Try to tests all revision conflict possibility
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick (base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.localNamespace = 'jio/local/revisionconflict/jiotests/';
o.rev={};
o.checkContent = function (string,message) {
ok (LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" is saved.');
};
o.checkNoContent = function (string,message) {
ok (!LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" does not exists.');
};
o.secondstorage_spec = {type:'local',
username:'revisionconflict',
applicationname:'jiotests'}
//////////////////////////////////////////////////////////////////////
o.jio = JIO.newJio({type:'conflictmanager',
storage:o.secondstorage_spec});
// create a new file
o.spy(o,'value',
{ok:true,id:'file.doc',rev:'1',conflicts:{total_rows:0,rows:[]},
revs_info:[{rev:'1',status:'available'}],
revisions:{start:1,ids:['1']}},
'new file "file.doc".');
o.jio.put(
{_id:'file.doc',content:'content1'},
{revs:true,revs_info:true,conflicts:true},
function (err,val) {
if (val) {
o.rev.first = val.rev;
val.rev = val.rev?val.rev.split('-')[0]:undefined;
if (val.revs_info) {
if (val.revisions) {
makeRevsAccordingToRevsInfo(
val.revisions,val.revs_info);
}
val.revs_info[0].rev =
val.revs_info[0].rev.split('-')[0];
}
}
o.f(err,val);
}
);
o.tick(o);
o.checkContent('file.doc.'+o.rev.first);
// modify the file
o.spy(o,'value',
{ok:true,id:'file.doc',rev:'2',
conflicts:{total_rows:0,rows:[]},
revisions:{start:2,ids:['2',getHashFromRev(o.rev.first)]},
revs_info:[{rev:'2',status:'available'}]},
'modify "file.doc", revision: "'+
o.rev.first+'".');
o.jio.put(
{_id:'file.doc',content:'content2',_rev:o.rev.first},
{revs:true,revs_info:true,conflicts:true},
function (err,val) {
if (val) {
o.rev.second = val.rev;
val.rev = val.rev?val.rev.split('-')[0]:undefined;
if (val.revs_info) {
if (val.revisions) {
makeRevsAccordingToRevsInfo(
val.revisions,val.revs_info);
}
val.revs_info[0].rev =
val.revs_info[0].rev.split('-')[0];
}
}
o.f(err,val);
}
);
o.tick(o);
o.checkContent('file.doc.'+o.rev.second);
o.checkNoContent('file.doc.'+o.rev.first);
// modify the file from the second revision instead of the third
o.test_message = 'modify "file.doc", revision: "'+
o.rev.first+'" -> conflict!';
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content3',_rev:o.rev.first},
{revs:true,revs_info:true,conflicts:true},function (err,val) {
o.f();
var k;
if (err) {
o.rev.third = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.tmp = err.conflicts;
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.third,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.second,o.rev.third],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:1,ids:[getHashFromRev(o.rev.third)]},
revs_info:[{rev:o.rev.second,status:'available'},
{rev:o.rev.third,status:'available'}]
},o.test_message);
ok (!revs_infoContains(err.revs_info,o.rev.first),
'check if the first revision is not include to '+
'the conflict list.');
ok (revs_infoContains(err.revs_info,err.rev),
'check if the new revision is include to '+
'the conflict list.');
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.third);
// loading test
o.spy(o,'value',{_id:'file.doc',_rev:o.rev.third,content:'content3',
_conflicts:o.tmp},
'loading "file.doc" -> conflict!');
o.jio.get('file.doc',{conflicts:true},function (err,val) {
var k;
if (val) {
if (val._conflicts && val._conflicts.rows) {
checkConflictRow (val._conflicts.rows[0]);
}
for (k in {'_creation_date':0,'_last_modified':0}) {
if (val[k]) {
delete val[k];
} else {
val[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
o.f(err,val);
});
o.tick(o);
if (!o.solveConflict) { return ok(false,'Cannot to continue the tests'); }
// solving conflict
o.spy(o,'value',{ok:true,id:'file.doc',rev:'3'},
'solve conflict "file.doc".');
o.solveConflict(
'content4',function (err,val) {
if (val) {
o.rev.forth = val.rev;
val.rev = val.rev?val.rev.split('-')[0]:undefined;
}
o.f(err,val);
});
o.tick(o);
o.checkContent('file.doc.'+o.rev.forth);
o.checkNoContent('file.doc.'+o.rev.second);
o.checkNoContent('file.doc.'+o.rev.third);
o.jio.stop();
});
test ('Conflict in a conflict solving', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick (base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.localNamespace = 'jio/local/conflictconflict/jiotests/';
o.rev={};
o.checkContent = function (string,message) {
ok (LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" is saved.');
};
o.checkNoContent = function (string,message) {
ok (!LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" does not exists.');
};
o.secondstorage_spec = {type:'local',
username:'conflictconflict',
applicationname:'jiotests'}
//////////////////////////////////////////////////////////////////////
o.jio = JIO.newJio({type:'conflictmanager',
storage:o.secondstorage_spec});
// create a new file
o.test_message = 'new file "file.doc", revision: "0".'
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content1'},
{conflicts:true,revs:true,revs_info:true},
function(err,val) {
o.f();
if (val) {
o.rev.first = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file.doc',rev:o.rev.first,
conflicts:{total_rows:0,rows:[]},
revisions:{start:1,ids:[getHashFromRev(o.rev.first)]},
revs_info:[{rev:o.rev.first,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.first);
// modify the file from the second revision instead of the third
o.test_message = 'modify "file.doc", revision: "0" -> conflict!';
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content2'},
{conflicts:true,revs:true,revs_info:true},
function (err,val) {
o.f();
var k;
if (err) {
o.rev.second = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.second,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.first,o.rev.second],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:1,ids:[getHashFromRev(o.rev.second)]},
revs_info:[{rev:o.rev.first,status:'available'},
{rev:o.rev.second,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.second);
if (!o.solveConflict) { return ok(false,'Cannot to continue the tests'); }
// saving another time
o.test_message = 'modify "file.doc" when solving, revision: "'+
o.rev.first+'" -> conflict!';
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content3',_rev:o.rev.first},
{conflicts:true,revs:true,revs_info:true},
function(err,val){
o.f();
if (err) {
o.rev.third = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.third,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.second,o.rev.third],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:2,ids:[getHashFromRev(o.rev.third),
getHashFromRev(o.rev.first)]},
revs_info:[{rev:o.rev.second,status:'available'},
{rev:o.rev.third,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.third);
o.checkNoContent ('file.doc.'+o.rev.first);
// solving first conflict
o.test_message = 'solving conflict "file.doc" -> conflict!';
o.f = o.t.spy();
o.solveConflict(
'content4',{conflicts:true,revs:true,revs_info:true},
function (err,val) {
o.f();
if (err) {
o.rev.forth = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.forth,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.third,o.rev.forth],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:2,ids:[getHashFromRev(o.rev.forth),
getHashFromRev(o.rev.second)]},
revs_info:[{rev:o.rev.third,status:'available'},
{rev:o.rev.forth,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.forth);
o.checkNoContent ('file.doc.'+o.rev.second);
if (!o.solveConflict) { return ok(false,'Cannot to continue the tests'); }
// solving last conflict
o.test_message = 'solving last conflict "file.doc".';
o.f = o.t.spy();
o.solveConflict(
'content5',{conflicts:true,revs:true,revs_info:true},
function (err,val) {
if (val) {
o.rev.fifth = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file.doc',rev:o.rev.fifth,
conflicts:{total_rows:0,rows:[]},
revisions:{start:3,ids:[getHashFromRev(o.rev.fifth),
getHashFromRev(o.rev.forth),
getHashFromRev(o.rev.second)]},
revs_info:[{rev:o.rev.fifth,status:'available'}]
},o.test_message);
o.f();
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.fifth);
o.jio.stop();
});
test ('Remove revision conflict', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick (base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.localNamespace = 'jio/local/removeconflict/jiotests/';
o.rev={};
o.checkContent = function (string,message) {
ok (LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" is saved.');
};
o.checkNoContent = function (string,message) {
ok (!LocalOrCookieStorage.getItem(o.localNamespace + string),
message || '"' + string + '" does not exists.');
};
o.secondstorage_spec = {type:'local',
username:'removeconflict',
applicationname:'jiotests'}
//////////////////////////////////////////////////////////////////////
o.jio = JIO.newJio({type:'conflictmanager',
storage:o.secondstorage_spec});
o.test_message = 'new file "file.doc", revision: "0".';
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content1'},
{conflicts:true,revs:true,revs_info:true},
function(err,val) {
o.f();
if (val) {
o.rev.first = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file.doc',rev:o.rev.first,
conflicts:{total_rows:0,rows:[]},
revisions:{start:1,ids:[getHashFromRev(o.rev.first)]},
revs_info:[{rev:o.rev.first,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.first);
o.test_message = 'remove "file.doc", revision: "wrong" -> conflict!';
o.f = o.t.spy();
o.jio.remove(
{_id:'file.doc'},
{conflicts:true,revs:true,revs_info:true,rev:'wrong'},
function (err,val) {
o.f();
if (err) {
o.rev.second = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.second,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.first,o.rev.second],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:1,ids:[getHashFromRev(o.rev.second)]},
revs_info:[{rev:o.rev.first,status:'available'},
{rev:o.rev.second,status:'deleted'}]
},o.test_message);
});
o.tick(o);
o.test_message = 'new file again "file.doc".';
o.f = o.t.spy();
o.jio.put(
{_id:'file.doc',content:'content2'},
{conflicts:true,revs:true,revs_info:true},
function (err,val) {
o.f();
if (err) {
o.rev.third = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.third,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.first,o.rev.second,o.rev.third],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:1,ids:[getHashFromRev(o.rev.third)]},
revs_info:[{rev:o.rev.first,status:'available'},
{rev:o.rev.second,status:'deleted'},
{rev:o.rev.third,status:'available'}]
},o.test_message);
});
o.tick(o);
o.checkContent ('file.doc.'+o.rev.third);
o.test_message = 'remove "file.doc", revision: "'+o.rev.first+
'" -> conflict!'
o.f = o.t.spy();
o.jio.remove(
{_id:'file.doc'},
{conflicts:true,revs:true,revs_info:true,rev:o.rev.first},
function (err,val) {
o.f();
if (err) {
o.rev.forth = err.rev;
err.rev = checkRev(err.rev);
if (err.conflicts && err.conflicts.rows) {
o.solveConflict = checkConflictRow (err.conflicts.rows[0]);
}
for (k in {'error':0,'message':0,'reason':0,'statusText':0}) {
if (err[k]) {
delete err[k];
} else {
err[k] = 'ERROR: ' + k + ' is missing !';
}
}
}
deepEqual(err||val,{
rev:o.rev.forth,
conflicts:{total_rows:1,rows:[
{id:'file.doc',key:[o.rev.second,o.rev.third,o.rev.forth],
value:{_solveConflict:'function'}}]},
status:409,
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:2,ids:[getHashFromRev(o.rev.forth),
getHashFromRev(o.rev.first)]},
revs_info:[{rev:o.rev.second,status:'deleted'},
{rev:o.rev.third,status:'available'},
{rev:o.rev.forth,status:'deleted'}]
},o.test_message);
});
o.tick(o);
o.checkNoContent ('file.doc.'+o.rev.first);
o.checkNoContent ('file.doc.'+o.rev.forth);
if (!o.solveConflict) { return ok(false, 'Cannot continue the tests'); }
o.test_message = 'solve "file.doc"';
o.f = o.t.spy();
o.solveConflict({conflicts:true,revs:true,revs_info:true},function(err,val){
o.f();
if (val) {
o.rev.fifth = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file.doc',rev:o.rev.fifth,
conflicts:{total_rows:0,rows:[]},
revisions:{start:3,ids:[getHashFromRev(o.rev.fifth),
getHashFromRev(o.rev.forth),
getHashFromRev(o.rev.first)]},
revs_info:[{rev:o.rev.fifth,status:'deleted'}]
},o.test_message);
});
o.tick(o);
o.checkNoContent ('file.doc.'+o.rev.second);
o.checkNoContent ('file.doc.'+o.rev.forth);
o.checkNoContent ('file.doc.'+o.rev.fifth);
o.test_message = 'save "file3.doc"';
o.f = o.t.spy();
o.jio.put(
{_id:'file3.doc',content:'content3'},
function(err,val) {
o.f();
if (val) {
o.rev.sixth = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file3.doc',rev:o.rev.sixth
},o.test_message);
});
o.tick(o);
o.test_message = 'save "file3.doc", rev "'+o.rev.sixth+'"';
o.f = o.t.spy();
o.jio.put(
{_id:'file3.doc',content:'content3',_rev:o.rev.sixth},
function(err,val) {
o.f();
if (val) {
o.rev.seventh = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file3.doc',rev:o.rev.seventh
},o.test_message);
});
o.tick(o);
o.test_message = 'remove last "file3.doc"';
o.f = o.t.spy();
o.jio.remove(
{_id:'file3.doc'},
{conflicts:true,revs:true,revs_info:true,rev:'last'},
function (err,val) {
o.f();
if (val) {
o.rev.eighth = val.rev;
val.rev = checkRev(val.rev);
}
deepEqual(err||val,{
ok:true,id:'file3.doc',
rev:o.rev.eighth,
conflicts:{total_rows:0,rows:[]},
// just one revision in the history, it does not keep older
// revisions because it is not a revision manager storage.
revisions:{start:3,ids:[getHashFromRev(o.rev.eighth),
getHashFromRev(o.rev.seventh),
getHashFromRev(o.rev.sixth)]},
revs_info:[{rev:o.rev.eighth,status:'deleted'}]
},o.test_message);
});
o.tick(o);
o.jio.stop();
});
test ('Load Revisions', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick (base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.secondstorage_spec = {type:'local',
username:'loadrevisions',
applicationname:'jiotests'}
//////////////////////////////////////////////////////////////////////
o.jio = JIO.newJio({type:'conflictmanager',
storage:o.secondstorage_spec});
o.spy(o,'status',404,'load file rev:1,','f'); // 12 === Replaced
o.spy(o,'status',404,'load file rev:2','g');
o.spy(o,'status',404,'and load file rev:3 at the same time','h');
o.jio.get('file',{rev:'1'},o.f);
o.jio.get('file',{rev:'2'},o.g);
o.jio.get('file',{rev:'3'},o.h);
o.tick(o,1000,'f'); o.tick(o,0,'g'); o.tick(o,0,'h');
o.jio.stop();
});
test ('Get revision List', function () {
var o = {}; o.clock = this.sandbox.useFakeTimers(); o.t = this;
o.clock.tick (base_tick);
o.spy = basic_spy_function;
o.tick = basic_tick_function;
o.secondstorage_spec = {type:'local',
username:'getrevisionlist',
applicationname:'jiotests'}
o.rev = {};
//////////////////////////////////////////////////////////////////////
o.jio = JIO.newJio({type:'conflictmanager',
storage:o.secondstorage_spec});
o.spy(o,'value',{total_rows:0,rows:[]},'Get revision list');
o.jio.allDocs(o.f);
o.tick(o);
o.spy(o,'value',{total_rows:0,rows:[],conflicts:{total_rows:0,rows:[]}},
'Get revision list with informations');
o.jio.allDocs({conflicts:true,revs:true,info_revs:true},o.f);
o.tick(o);
o.spy(o,'jobstatus','done','saving file');
o.jio.put({_id:'file',content:'content file'},function (err,val) {
o.rev.file1 = val?val.rev:undefined;
o.f(err,val);
});
o.tick(o);
o.spy(o,'jobstatus','done','saving memo');
o.jio.put({_id:'memo',content:'content memo'},function (err,val) {
o.rev.memo1 = val?val.rev:undefined;
o.f(err,val);
});
o.tick(o);
o.spy(o,'status',409,'saving memo conflict');
o.jio.put({_id:'memo',content:'content memo'},function (err,val) {
o.rev.memo2 = err?err.rev:undefined;
o.f(err,val);
});
o.tick(o);
o.f = o.t.spy();
o.jio.allDocs(function (err,val) {
var i;
if (val) {
for (i = 0; i < val.total_rows; i+= 1) {
val.rows[i].value._creation_date =
val.rows[i].value._creation_date?true:undefined;
val.rows[i].value._last_modified =
val.rows[i].value._last_modified?true:undefined;
o.rev[i] = checkRev (val.rows[i].value._rev);
}
}
deepEqual(err||val,{total_rows:2,rows:[{
id:'file',key:'file',value:{
_creation_date:true,_last_modified:true,_rev:o.rev[0]
}
},{
id:'memo',key:'memo',value:{
_creation_date:true,_last_modified:true,_rev:o.rev[1]
}
}]},'Get revision list after adding 2 files');
o.f();
});
o.tick(o);
o.f = o.t.spy();
o.jio.allDocs(
{conflicts:true,revs:true,revs_info:true},
function (err,val) {
var i;
if (val) {
for (i = 0; i < val.total_rows; i+= 1) {
val.rows[i].value._creation_date =
val.rows[i].value._creation_date?true:undefined;
val.rows[i].value._last_modified =
val.rows[i].value._last_modified?true:undefined;
if (val.conflicts && val.conflicts.rows) {
o.solveConflict =
checkConflictRow (val.conflicts.rows[0]);
}
}
}
deepEqual(err||val,{
total_rows:2,rows:[{
id:'file',key:'file',value:{
_creation_date:true,_last_modified:true,
_revisions:{start:1,ids:[getHashFromRev(o.rev.file1)]},
_rev:o.rev.file1,_revs_info:[{
rev:o.rev.file1,status:'available'
}]
}
},{
id:'memo',key:'memo',value:{
_creation_date:true,_last_modified:true,
_revisions:{start:1,ids:[getHashFromRev(o.rev.memo2)]},
_rev:o.rev.memo2,_revs_info:[{
rev:o.rev.memo1,status:'available'
},{
rev:o.rev.memo2,status:'available'
}]
}
}],
conflicts:{total_rows:1,rows:[{
id:'memo',key:[o.rev.memo1,o.rev.memo2],
value:{_solveConflict:'function'}
}]}
},'Get revision list with informations after adding 2 files');
o.f();
});
o.tick(o);
o.jio.stop();
});
}; // end thisfun
if (window.requirejs) {
require.config ({
paths: {
jiotestsloader: './jiotests.loader',
LocalOrCookieStorage: './testlocalorcookiestorage',
jQueryAPI: '../lib/jquery/jquery',
jQuery: '../js/jquery.requirejs_module',
JIO: '../src/jio',
Base64API: '../lib/base64/base64',
Base64: '../js/base64.requirejs_module',
JIODummyStorages: '../src/jio.dummystorages',
JIOStorages: '../src/jio.storage',
SJCLAPI:'../lib/sjcl/sjcl.min',
SJCL:'../js/sjcl.requirejs_module'
}
});
require(['jiotestsloader'],thisfun);
} else {
thisfun ({LocalOrCookieStorage:LocalOrCookieStorage,
JIO:jIO,
sjcl:sjcl,
Base64:Base64,
jQuery:jQuery});
}
}());
define ('jiotestsloader',[
'LocalOrCookieStorage','JIO','Base64','SJCL',
'jQuery','JIODummyStorages','JIOStorages'],
function (LocalOrCookieStorage,JIO,Base64,sjcl,jQuery) {
return {
LocalOrCookieStorage: LocalOrCookieStorage,
JIO: JIO,
sjcl: sjcl,
Base64: Base64,
jQuery: jQuery
};
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>JIO QUnit/Sinon Test</title>
<link rel="stylesheet" href="../lib/qunit/qunit.css" />
</head>
<body>
<div id="qunit"></div>
<script type="text/javascript" src="../lib/qunit/qunit.js"></script>
<script type="text/javascript" src="../lib/sinon/sinon.js"></script>
<script type="text/javascript" src="../lib/sinon/sinon-qunit.js"></script>
<script type="text/javascript" src="../lib/jquery/jquery.min.js"></script>
<script type="text/javascript" src="./testlocalorcookiestorage.js"></script>
<script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../lib/base64/base64.js"></script>
<script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script>
<script type="text/javascript" src="../lib/jsSha2/sha2.js"></script>
<script type="text/javascript" src="../src/jio.dummystorages.js"></script>
<script type="text/javascript" src="../jio.storage.js"></script>
<script type="text/javascript" src="./jiotests.js"></script>
</body>
</html>
<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/davgetlist/jiotests/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2012-05-02T12:48:33Z</lp1:creationdate>
<lp1:getlastmodified>Wed, 02 May 2012 12:48:33 GMT</lp1:getlastmodified>
<lp1:getetag>"1000-4bf0d1aeb9e43"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/davgetlist/jiotests/file</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2012-05-02T12:48:31Z</lp1:creationdate>
<lp1:getcontentlength>201</lp1:getcontentlength>
<lp1:getlastmodified>Wed, 02 May 2012 12:48:27 GMT</lp1:getlastmodified>
<lp1:getetag>"c9-4bf0d1a845df9"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/davgetlist/jiotests/memo</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2012-05-01T17:41:13Z</lp1:creationdate>
<lp1:getcontentlength>223</lp1:getcontentlength>
<lp1:getlastmodified>Wed, 02 May 2012 10:48:33 GMT</lp1:getlastmodified>
<lp1:getetag>"c9-4bf0d1aeb9e43"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/davload/jiotests/file</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2012-05-02T10:06:42Z</lp1:creationdate>
<lp1:getcontentlength>201</lp1:getcontentlength>
<lp1:getlastmodified>Wed, 02 May 2012 10:06:39 GMT</lp1:getlastmodified>
<lp1:getetag>"c9-4bf0ad7e45226"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>/davsave/jiotests/file</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>Thu, 01 Jan 1970 00:00:05 GMT</lp1:creationdate>
<lp1:getcontentlength>7</lp1:getcontentlength>
<lp1:getlastmodified>Thu, 01 Jan 1970 00:00:07 GMT</lp1:getlastmodified>
<lp1:getetag>"c9-4bf0ad7e45226"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
var LocalOrCookieStorage = (function () {
var tmp = function () {
this.storage = {};
};
tmp.prototype = {
getItem: function (k) {
var v = (typeof this.storage[k] === 'undefined' ?
null: this.storage[k]);
return JSON.parse (v);
},
setItem: function (k,v) {
this.storage[k] = JSON.stringify (v);
},
deleteItem: function (k) {
delete this.storage[k];
},
getAll: function () {
return this.storage;
}
};
return new tmp();
}());
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment