Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/Wing/Wing.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import "js.jsx";
import "js/web.jsx";
import "./WingCache.jsx";
import "./WingManifest.jsx";
import "./WingURL.jsx";

class Wing {
var _cache = null:WingCache;
var _options = {
verbose: false, // Boolean(= false): verbose mode
cache_busting: false, // Boolean(= false): add cache busting parameter. a.png?t=123
manifest: null, // Object/URLString(= null): manifest, or remote manifest file
hook_url: null:function(url:string):string
}:Map.<variant>;

function constructor(option:Map.<variant> = null) {
if (option) {
for (var key in option) {
this._options[key] = option[key];
}
}
this._cache = new WingCache(this._options["verbose"] as boolean,
this._options["cache_busting"] as boolean);
}
function init(cacheReadyCallback:function(err:Error):void):void {
var that = this;
var manifest = new WingManifest();

if (this._options["hook_url"]) {
manifest.setHook(this._options["hook_url"] as function(url:string):string);
}
if (typeof (this._options["manifest"] as variant) == "string") { // use local manifest
var url = this._options["manifest"] as string; // remote manifest

if (this._options["cache_busting"] as boolean) {
url = WingURL.cacheBusting(url);
}
manifest.load(url, function(err:Error):void {
that._cache.init(manifest.clone(), function(err:Error):void {
cacheReadyCallback(err);
});
});
} else {
manifest.set(this._options["manifest"] as Map.<variant>);
this._cache.init(manifest.clone(), function(err:Error):void {
cacheReadyCallback(err);
});
}
}
function is(id:string):boolean { // is valid id
return this._cache.is(id);
}
function has(id:string):boolean { // has memory cached
return this._cache.has(id);
}
function fetch(id:string, fetchedCallback:function(err:Error, id:string):void):void {
this._cache.fetch(id, fetchedCallback);
}
function fetchAll(fetchdCallback:function(err:Error, id:Array.<string>):void):void {
this._cache.fetchAll(fetchdCallback);
}
function get(id:string, toDataURI:boolean = false):string {
return this._cache.get(id, toDataURI);
}
function clear(callback:function(err:Error):void = null):void {
this._cache.clear(function(err:Error):void {
if (callback) {
callback(err);
}
});
}
}

134 changes: 134 additions & 0 deletions src/Wing/WingBase64.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import "js.jsx";
import "js/web.jsx";

class WingBase64 {
static var _indexToChar =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");
static var _charToIndex = {
A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9,
K: 10, L: 11, M: 12, N: 13, O: 14, P: 15, Q: 16, R: 17, S: 18, T: 19,
U: 20, V: 21, W: 22, X: 23, Y: 24, Z: 25,
a: 26, b: 27, c: 28, d: 29, e: 30, f: 31, g: 32, h: 33, i: 34, j: 35,
k: 36, l: 37, m: 38, n: 39, o: 40, p: 41, q: 42, r: 43, s: 44, t: 45,
u: 46, v: 47, w: 48, x: 49, y: 50, z: 51,
0: 52, 1: 53, 2: 54, 3: 55, 4: 56, 5: 57, 6: 58, 7: 59, 8: 60, 9: 61,
"+": 62, "-": 62, // URLSafe64 chars
"/": 63, "_": 63, // URLSafe64 chars
"=": 0 // padding
}:Map.<int>;

static function btoa(binary:string,
normalize:boolean = false):string {
var window = js.global["window"] as __noconvert__ Window;

if (window.btoa) {
if (!normalize) {
try {
return window.btoa(binary); // BinaryString to Base64String
} catch (o_o:Error) {
// maybe. xhr response data has non ascii values.
}
}
return window.btoa( WingBinaryString.normalize(binary, 0xff) );
}
return WingBase64.encode( WingBinaryString.toArray(binary, 0xff) );
}
static function atob(base64:string):string {
var window = js.global["window"] as __noconvert__ Window;

if (window.atob) {
try {
return window.atob(base64);
} catch (o_o:Error) {
// maybe. broken base64 data
}
}
return WingBinaryString.fromArray( WingBase64.decode(base64) );
}
static function encode(ary:Array.<int>,
safe:boolean = false):string {
var rv = []:Array.<string>;
var c = 0;
var i = -1;
var iz = ary.length;
var pad = [0, 2, 1][iz % 3];
var chars = WingBase64._indexToChar;

// 24bit binary string -> 32bit base64 binary string
--iz;
while (i < iz) {
c = ((ary[++i] & 0xff) << 16) |
((ary[++i] & 0xff) << 8) |
(ary[++i] & 0xff); // 24bit

rv.push(chars[(c >> 18) & 0x3f],
chars[(c >> 12) & 0x3f],
chars[(c >> 6) & 0x3f],
chars[ c & 0x3f]);
}
pad > 1 && (rv[rv.length - 2] = "=");
pad > 0 && (rv[rv.length - 1] = "=");
if (safe) {
return rv.join("").replace(/\=+$/g, "").replace(/\+/g, "-").
replace(/\//g, "_");
}
return rv.join("");
}
static function decode(str:string):Array.<int> {
var rv = []:Array.<int>;
var c = 0;
var i = 0;
var ary = str.split("");
var iz = str.length - 1;
var codes = WingBase64._charToIndex;

// 32bit base64 binary string -> 24bit binary string
while (i < iz) { // 00000000|00000000|00000000 (24bit)
c = (codes[ary[i++]] << 18) // 111111 | |
| (codes[ary[i++]] << 12) // 11|1111 |
| (codes[ary[i++]] << 6) // | 1111|11
| codes[ary[i++]]; // | | 111111
// v v v
rv.push((c >> 16) & 0xff, // --------
(c >> 8) & 0xff, // --------
c & 0xff); // --------
}
rv.length -= [0, 0, 2, 1][str.replace(/\=+$/, "").length % 4]; // cut tail

return rv;
}
}

class WingBinaryString {
static function toArray(binary:string,
filter:int = 0xffff):Array.<int> {
var i = 0, iz = binary.length, rv = new Array.<int>(iz);

for (; i < iz; ++i) {
rv[i] = binary.charCodeAt(i) & filter;
}
return rv;
}
static function fromArray(ary:Array.<int>):string {
var rv = []:Array.<string>, i = 0, iz = ary.length, bulkSize = 32000;

// Avoid String.fromCharCode.apply(null, BigArray) exception
if (iz < bulkSize) {
//return String.fromCharCode.apply(null, ary);
return js.invoke(String, "fromCharCode", ary as __noconvert__ Array.<variant>) as string;
}
for (; i < iz; i += bulkSize) {
//rv.push( String.fromCharCode.apply(null, ary.slice(i, i + bulkSize)) );
rv.push(
js.invoke(String, "fromCharCode",
ary.slice(i, i + bulkSize) as __noconvert__ Array.<variant>) as string );

}
return rv.join("");
}
static function normalize(binary:string,
filter:int = 0xffff):string {
return WingBinaryString.fromArray( WingBinaryString.toArray(binary, filter) );
}
}

131 changes: 131 additions & 0 deletions src/Wing/WingCache.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import "./WingStorage.jsx";
import "./WingMimeType.jsx";
import "./WingXHR.jsx";
import "./WingBase64.jsx";

class WingCache {
var _assets = {}:Map.<variant>;
var _storage = null:WingStorage;
var _optionVerbose = false;
var _optionCacheBusting = false;

function constructor(verbose:boolean, cacheBusting:boolean) {
this._optionVerbose = verbose;
this._optionCacheBusting = cacheBusting;
}
function init(assets:Map.<variant>, cacheReadyCallback:function(err:Error):void):void {
this._assets = assets;
this._storage = new WingStorage(assets, this._optionVerbose);
// --- add properties ---
for (var id in this._assets) {
this._assets[id]["L1"] = false;
this._assets[id]["L2"] = false;
this._assets[id]["cache"] = "";
}
// --- fetch L2 -> L1 cache, remove old cache ---
this._storage.init(function(err:Error) {
cacheReadyCallback(err);
});
}
function is(id:string):boolean {
return id in this._assets;
}
function has(id:string):boolean {
if (this.is(id)) {
return this._assets[id]["L1"] as boolean;
}
return false;
}
function fetch(id:string, fetchedCallback:function(err:Error, id:string):void):void {
if (!this.is(id)) {
fetchedCallback(new Error("NOT_FOUND"), id);
return;
}
var that = this;
var L1 = this._assets[id]["L1"] as boolean;
var L2 = this._assets[id]["L2"] as boolean;

if (L1) {
fetchedCallback(null, id);
} else if (L2) { // already Lv2 cached
this._storage.fetch(id, function(err:Error, id:string, cache:string):void {
if (!err) {
that._assets[id]["L1"] = true; // fetch L2 -> L1
that._assets[id]["cache"] = cache;
}
fetchedCallback(err, id);
});
} else {
var url = this._assets[id]["url"] as string;
var binary = WingMimeType.isBinary(this._assets[id]["mime"] as string);

WingXHR.load(url, binary, this._optionCacheBusting, function(err:Error, cache:string):void {
if (err) {
fetchedCallback(err, id);
} else {
// binary encode
switch ( this._assets[id]["code"] as int ) {
case 1: break; // TEXT
case 2: cache = WingBase64.btoa(cache, true); break; // BIN->BASE64
case 3: throw new Error("DOUBLER NOT IMPL"); // BIN->DOUBLER
}
that._storage.store(id, cache, function(err:Error):void {
if (!err) {
that._assets[id]["L1"] = true;
that._assets[id]["L2"] = true;
that._assets[id]["cache"] = cache;
if (that._assets[id]["db"] as int == 0) {
that._assets[id]["L2"] = false; // onMemoryStorage
}
}
fetchedCallback(err, id);
});
}
});
}
}
function fetchAll(fetchdCallback:function(err:Error, id:Array.<string>):void):void {
var that = this;
var ids = this._assets.keys();
var remain = ids.length;

function _fin(err:Error):void {
fetchdCallback(err, ids);
}
ids.forEach(function(id):void {
that.fetch(id, function(err:Error, id:string):void {
if (err) {
remain = 0;
_fin(err);
return;
}
if (--remain <= 0) {
_fin(null);
}
});
});
}
function get(id:string, toDataURI:boolean = false):string {
if (!this.is(id)) {
return "";
}
if (toDataURI) {
var mime = "";
switch ( this._assets[id]["code"] as int ) {
case 2: // BASE64
mime = WingMimeType.toMimeType(this._assets[id]["mime"] as string);
return "data:" + mime + ";base64," + (this._assets[id]["cache"] as string);
}
}
return this._assets[id]["cache"] as string;
}
function clear(callback:function(err:Error):void):void {
for (var id in this._assets) {
this._assets[id]["L1"] = false;
this._assets[id]["L2"] = false;
this._assets[id]["cache"] = "";
}
this._storage.clear(callback);
}
}

Loading