package ru.m.storage; import haxe.Unserializer; import flash.net.SharedObject; import haxe.crypto.Md5; import haxe.io.Bytes; import haxe.Serializer; import promhx.Promise; import ru.m.data.DataSource; typedef DataMeta = Map; typedef IdMeta = { var id:I; var meta:DataMeta; } interface DataHelper { public function idKey(id:I):String; public function keyId(key:String):I; public function dataId(data:D):I; public function dataBytes(data:D):Bytes; public function bytesData(bytes:Bytes):D; public function dataMeta(data:D):DataMeta; } class DefaultHelper implements DataHelper { public function new() { } public function idKey(id:String):String { return id; } public function keyId(key:String):String { return key; } public function dataId(data:Bytes):String { return Md5.make(data).toHex(); } public function dataBytes(data:Bytes):Bytes { return data; } public function bytesData(bytes:Bytes):Bytes { return bytes; } public function dataMeta(data:Bytes):DataMeta { return ["date" => Date.now().toString()]; } } class SharedObjectDataStorage implements DataStorage { private static var DATA_KEY:String = "data"; public var name(default, null):String; public var version(default, null):Int; private var helper:DataHelper; private var _indexObject:SharedObject; private var indexObject(get, null):SharedObject; private function get_indexObject():SharedObject { if (_indexObject == null) { _indexObject = SharedObject.getLocal('${name}/${version}/index'); } return _indexObject; } public function new(name:String, helper:DataHelper, version:Int = 1) { this.name = name; this.version = version; this.helper = helper; } private function resolveDataObject(id:I):SharedObject { var idKey = helper.idKey(id); return SharedObject.getLocal('${name}/${version}/${Md5.encode(idKey)}'); } private function applyFilter(filter:Null, meta:DataMeta):Bool { if (filter != null) { for (k in filter.keys()) { if (meta.exists(k) && meta.get(k) != filter.get(k)) { return false; } } } return true; } private function applyOrder(order:Order, values:Array>):Void { if (order != null) { values.sort((a:IdMeta, b:IdMeta) -> { for (item in order) { var av = a.meta.get(item.field); var bv = b.meta.get(item.field); if (av > bv) { return item.reverse ? -1 : 1; } else if (av < bv) { return item.reverse ? 1 : -1; } } return 0; }); } } public function getIndexPage(page:Page):Promise> { var data = indexObject.data; var values:Array> = []; for (k in Reflect.fields(data)) { var meta = Unserializer.run(Reflect.field(data, k)); if (applyFilter(page.filter, meta)) { values.push({id: helper.keyId(k), meta: meta}); } } applyOrder(page.order, values); var result:Array = values.slice(page.index * page.count, page.index * page.count + page.count).map(value -> value.id); return Promise.promise({ page: page, total: values.length, data: result, }); } public function getPage(page:Page):Promise> { return getIndexPage(page).pipe((indexPage:DataPage) -> { return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array) -> { return { page: indexPage.page, total: indexPage.total, data: data, } }); }); } public function get(id:I):Promise { var dataObject = resolveDataObject(id); if (Reflect.hasField(dataObject.data, DATA_KEY)) { return Promise.promise(helper.bytesData(Bytes.ofHex(Reflect.field(dataObject.data, DATA_KEY)))); } return Promise.promise(null); } public function save(item:D):Promise { var id = helper.dataId(item); var dataObject = resolveDataObject(id); dataObject.setProperty(DATA_KEY, helper.dataBytes(item).toHex()); dataObject.flush(); indexObject.setProperty(helper.idKey(id), Serializer.run(helper.dataMeta(item))); indexObject.flush(); return Promise.promise(item); } public function delete(id:I):Promise { var dataObject = resolveDataObject(id); if (Reflect.hasField(dataObject.data, DATA_KEY)) { dataObject.clear(); return Promise.promise(true); } Reflect.deleteField(dataObject.data, helper.idKey(id)); return Promise.promise(false); } }