package ru.m.data; import flash.net.SharedObject; import haxe.DynamicAccess; import haxe.io.Bytes; import haxe.Serializer; import haxe.Unserializer; import hw.connect.PacketUtil; import promhx.Promise; import ru.m.data.IDataSource; class Converter { private var serializer:D -> S; private var desirealizer:S -> D; public function new(serializer:D -> S, desirealizer:S -> D) { this.serializer = serializer; this.desirealizer = desirealizer; } public inline function serialize(item:D):S { return serializer(item); } public inline function deserialize(data:S):D { return desirealizer(data); } } class DefaultConverter extends Converter { public function new () { super( item -> Serializer.run(item), data -> new Unserializer(data).unserialize() ); } } class ProtoConverter extends Converter { public function new(messageClass:Class) { super( item -> PacketUtil.toBytes(item).toHex(), data -> PacketUtil.fromBytes(Bytes.ofHex(data), messageClass) ); } } class EmptyConverter extends Converter { public function new() { super(item -> item, data -> data); } } class MetaBuilder extends DefaultConverter { private var builder:D -> DataMeta; public function new(builder:D -> DataMeta) { super(); this.builder = builder; } public function build(item:D):DataMeta { return builder(item); } } typedef DataMeta = Filter; typedef IdValue = {id:I, meta:DataMeta}; class DataStorage implements IDataManager implements IDataIndex { inline private static var DATA_KEY = "item"; private var name:String; private var metaBuilder:MetaBuilder; private var idConverter:Converter; private var dataConverter:Converter; private var indexData:SharedObject; public function new( name:String, metaBuilder:MetaBuilder, idConverter:Converter = null, dataConverter:Converter = null ) { this.name = name; this.metaBuilder = metaBuilder; this.idConverter = idConverter != null ? idConverter : new DefaultConverter(); this.dataConverter = dataConverter != null ? dataConverter : new DefaultConverter(); this.indexData = SharedObject.getLocal('${name}/index'); } private function serializeId(id:I):String { return idConverter.serialize(id); } private function desirealizeId(data:String):I { return idConverter.deserialize(data); } private function buildMeta(item:D):DataMeta { return metaBuilder.build(item); } private function checkFilter(filter:Null, meta:DataMeta):Bool { if (filter != null) { for (k => v in filter) { if (meta.exists(k) && meta.get(k) != v) { return false; } } } return true; } private function sort(order:Order, values:Array>):Void { if (order != null) { values.sort((a:IdValue, b:IdValue) -> { for (item in order) { var av = a.meta.get(item.key); var bv = b.meta.get(item.key); if (av > bv) { return item.reverse ? - 1 : 1; } else if (av < bv) { return item.reverse ? 1 : -1; } } return 0; }); } } private function serialize(item:D):Dynamic { return dataConverter.serialize(item); } private function unserialize(data:Dynamic):D { return dataConverter.deserialize(data); } public function getIndexPage(page:Page):Promise> { var data:DynamicAccess = indexData.data; var values:Array> = []; for (k => v in data) { var meta = metaBuilder.deserialize(v); if (checkFilter(page.filter, meta)) { values.push({id: desirealizeId(k), meta: meta}); } } sort(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 stringId = serializeId(id); var itemData = SharedObject.getLocal('${name}/${stringId}'); var result:D = null; if (Reflect.hasField(itemData.data, DATA_KEY)) { result = unserialize(Reflect.field(itemData.data, DATA_KEY)); } return Promise.promise(result); } public function save(item:D):Promise { var meta = metaBuilder.build(item); var stringId = meta.get("id"); var itemData = SharedObject.getLocal('${name}/${stringId}'); itemData.setProperty(DATA_KEY, serialize(item)); itemData.flush(); indexData.setProperty(stringId, metaBuilder.serialize(meta)); indexData.flush(); return Promise.promise(item); } public function delete(id:I):Promise { var stringId = serializeId(id); var itemData = SharedObject.getLocal('${name}/${stringId}'); itemData.clear(); var data:DynamicAccess = indexData.data; data.remove(stringId); indexData.flush(); return Promise.promise(true); } }