[refactoring] restore FileStorage
This commit is contained in:
@@ -2,12 +2,17 @@ package ru.m.data;
|
|||||||
|
|
||||||
import promhx.Promise;
|
import promhx.Promise;
|
||||||
|
|
||||||
typedef PageParams = Map<String, Dynamic>;
|
typedef Filter = Map<String, Dynamic>;
|
||||||
|
typedef Order = Array<{
|
||||||
|
var field:String;
|
||||||
|
@:optional var reverse:Bool;
|
||||||
|
}>;
|
||||||
|
|
||||||
typedef Page = {
|
typedef Page = {
|
||||||
var index:Int;
|
var index:Int;
|
||||||
var count:Int;
|
var count:Int;
|
||||||
@:optional var params:PageParams;
|
@:optional var filter:Filter;
|
||||||
|
@:optional var order:Order;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef DataPage<D> = {
|
typedef DataPage<D> = {
|
||||||
@@ -18,5 +23,11 @@ typedef DataPage<D> = {
|
|||||||
|
|
||||||
interface DataSource<D> {
|
interface DataSource<D> {
|
||||||
public function getPage(page:Page):Promise<DataPage<D>>;
|
public function getPage(page:Page):Promise<DataPage<D>>;
|
||||||
// public function get(id):D;
|
}
|
||||||
|
|
||||||
|
interface DataStorage<D, I> extends DataSource<D> {
|
||||||
|
public function getIndexPage(page:Page):Promise<DataPage<I>>;
|
||||||
|
public function get(id:I):Promise<D>;
|
||||||
|
public function save(item:D):Promise<D>;
|
||||||
|
public function delete(id:I):Promise<Bool>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ru.m.puzzlez.image.ImageSourceBundle;
|
|||||||
import ru.m.puzzlez.render.part.IPartBuilder;
|
import ru.m.puzzlez.render.part.IPartBuilder;
|
||||||
import ru.m.puzzlez.settings.Settings;
|
import ru.m.puzzlez.settings.Settings;
|
||||||
import ru.m.puzzlez.source.AssetImageSource;
|
import ru.m.puzzlez.source.AssetImageSource;
|
||||||
|
import ru.m.puzzlez.source.FileImageSource;
|
||||||
import ru.m.puzzlez.source.PixabayImageSource;
|
import ru.m.puzzlez.source.PixabayImageSource;
|
||||||
import ru.m.puzzlez.view.PuzzlezAppView;
|
import ru.m.puzzlez.view.PuzzlezAppView;
|
||||||
import ru.m.update.Updater;
|
import ru.m.update.Updater;
|
||||||
@@ -21,6 +22,7 @@ class PuzzlezApp {
|
|||||||
Settings;
|
Settings;
|
||||||
IPartBuilder;
|
IPartBuilder;
|
||||||
sourceBundle.register(new AssetImageSource());
|
sourceBundle.register(new AssetImageSource());
|
||||||
|
sourceBundle.register(new FileImageSource());
|
||||||
sourceBundle.register(new PixabayImageSource());
|
sourceBundle.register(new PixabayImageSource());
|
||||||
L.push(new TraceLogger());
|
L.push(new TraceLogger());
|
||||||
updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json");
|
updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json");
|
||||||
|
|||||||
30
src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx
Normal file
30
src/app/haxe/ru/m/puzzlez/source/FileImageSource.hx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package ru.m.puzzlez.source;
|
||||||
|
|
||||||
|
import promhx.Promise;
|
||||||
|
import ru.m.data.DataSource;
|
||||||
|
import ru.m.puzzlez.image.ImageSource;
|
||||||
|
import ru.m.puzzlez.proto.game.ImageId;
|
||||||
|
import ru.m.puzzlez.storage.FileStorage;
|
||||||
|
|
||||||
|
class FileImageSource implements ImageSource {
|
||||||
|
public var id(default, never):String = "file";
|
||||||
|
|
||||||
|
@:provide private var storage:FileStorage;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(page:Page):Promise<DataPage<ImageId>> {
|
||||||
|
return storage.getIndexPage(page).then((response:DataPage<String>) -> {
|
||||||
|
return {
|
||||||
|
page: response.page,
|
||||||
|
data: response.data.map(key -> new ImageId().setSource(id).setId(key)),
|
||||||
|
total: response.total,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(id:String):Promise<ImageValue> {
|
||||||
|
return storage.get(id).then(bytes -> ImageValue.BYTES(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ class PixabayImageSource implements ImageSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getPage(page:Page):Promise<DataPage<ImageId>> {
|
public function getPage(page:Page):Promise<DataPage<ImageId>> {
|
||||||
return this.api.getPage(page.index + 1, page.count, page.params.get("category")).then((response:PixabayResponse) -> {
|
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")).then((response:PixabayResponse) -> {
|
||||||
var data:Array<ImageId> = [];
|
var data:Array<ImageId> = [];
|
||||||
for (hit in response.hits) {
|
for (hit in response.hits) {
|
||||||
imageUrlsCache.set(hit.id, hit.largeImageURL);
|
imageUrlsCache.set(hit.id, hit.largeImageURL);
|
||||||
|
|||||||
11
src/app/haxe/ru/m/puzzlez/storage/FileStorage.hx
Normal file
11
src/app/haxe/ru/m/puzzlez/storage/FileStorage.hx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.m.puzzlez.storage;
|
||||||
|
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
import ru.m.storage.SharedObjectDataStorage;
|
||||||
|
|
||||||
|
@:provide class FileStorage extends SharedObjectDataStorage<Bytes, String> {
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super("file", new DefaultHelper());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package ru.m.puzzlez.view;
|
|||||||
import hw.view.form.LabelView;
|
import hw.view.form.LabelView;
|
||||||
import hw.view.frame.FrameSwitcher;
|
import hw.view.frame.FrameSwitcher;
|
||||||
import hw.view.frame.FrameView;
|
import hw.view.frame.FrameView;
|
||||||
import ru.m.data.DataSource.PageParams;
|
import ru.m.data.DataSource;
|
||||||
import ru.m.puzzlez.image.ImageSourceBundle;
|
import ru.m.puzzlez.image.ImageSourceBundle;
|
||||||
import ru.m.puzzlez.proto.game.ImageId;
|
import ru.m.puzzlez.proto.game.ImageId;
|
||||||
import ru.m.puzzlez.view.common.ImageDataList;
|
import ru.m.puzzlez.view.common.ImageDataList;
|
||||||
@@ -11,7 +11,7 @@ import ru.m.puzzlez.view.common.ImageDataList;
|
|||||||
typedef ImageSourceConfig = {
|
typedef ImageSourceConfig = {
|
||||||
var title:String;
|
var title:String;
|
||||||
var sourceId:String;
|
var sourceId:String;
|
||||||
@:optional var params:PageParams;
|
@:optional var filter:Filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:template class ImageSourceFrame extends FrameView<ImageSourceConfig> {
|
@:template class ImageSourceFrame extends FrameView<ImageSourceConfig> {
|
||||||
@@ -30,15 +30,13 @@ typedef ImageSourceConfig = {
|
|||||||
images.reset();
|
images.reset();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
header.text = data.title;
|
header.text = data.title;
|
||||||
images.page.params = data.params;
|
images.page.filter = data.filter;
|
||||||
images.source = sourceBundle.get(data.sourceId);
|
images.source = sourceBundle.get(data.sourceId);
|
||||||
images.refresh();
|
images.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function start(imageId:ImageId):Void {
|
private function start(imageId:ImageId):Void {
|
||||||
//var state = GameUtil.buildState(GameUtil.buildPreset(imageId, 2, 2));
|
|
||||||
//switcher.change(GameFrame.ID, state);
|
|
||||||
switcher.change(PresetFrame.ID, imageId);
|
switcher.change(PresetFrame.ID, imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import hw.view.form.ButtonView;
|
|||||||
import hw.view.frame.FrameSwitcher;
|
import hw.view.frame.FrameSwitcher;
|
||||||
import hw.view.frame.FrameView;
|
import hw.view.frame.FrameView;
|
||||||
import ru.m.pixabay.PixabayApi;
|
import ru.m.pixabay.PixabayApi;
|
||||||
|
import ru.m.puzzlez.FileUtil;
|
||||||
|
import ru.m.puzzlez.storage.FileStorage;
|
||||||
import ru.m.puzzlez.view.ImageSourceFrame;
|
import ru.m.puzzlez.view.ImageSourceFrame;
|
||||||
import ru.m.update.Updater;
|
import ru.m.update.Updater;
|
||||||
|
|
||||||
@@ -16,14 +18,17 @@ import ru.m.update.Updater;
|
|||||||
|
|
||||||
@:provide var switcher:FrameSwitcher;
|
@:provide var switcher:FrameSwitcher;
|
||||||
@:provide static var appUpdater:Updater;
|
@:provide static var appUpdater:Updater;
|
||||||
|
@:provide var fileStorage:FileStorage;
|
||||||
|
|
||||||
|
private var fileSource:ImageSourceConfig = {title: "Files", sourceId: "file"};
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super(ID);
|
super(ID);
|
||||||
var data:Array<ImageSourceConfig> = [];
|
var data:Array<ImageSourceConfig> = [];
|
||||||
data.push({title: "Assets", sourceId: "asset"});
|
data.push({title: "Assets", sourceId: "asset"});
|
||||||
// data.push({title: "Files", sourceId: "file"});
|
data.push(fileSource);
|
||||||
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
|
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
|
||||||
data.push({title: type, sourceId: "pixabay", params: ["category" => type]});
|
data.push({title: type, sourceId: "pixabay", filter: ["category" => type]});
|
||||||
}
|
}
|
||||||
sources.data = data;
|
sources.data = data;
|
||||||
}
|
}
|
||||||
@@ -34,6 +39,12 @@ import ru.m.update.Updater;
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function upload():Void {
|
||||||
|
FileUtil.browse()
|
||||||
|
.pipe((data:FileContent) -> fileStorage.save(data.content))
|
||||||
|
.then(_ -> showSource(fileSource));
|
||||||
|
}
|
||||||
|
|
||||||
public function showSource(source:ImageSourceConfig):Void {
|
public function showSource(source:ImageSourceConfig):Void {
|
||||||
switcher.change(ImageSourceFrame.ID, source);
|
switcher.change(ImageSourceFrame.ID, source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ views:
|
|||||||
+onDataSelect: ~showSource
|
+onDataSelect: ~showSource
|
||||||
geometry.margin: 5
|
geometry.margin: 5
|
||||||
overflow.y: scroll
|
overflow.y: scroll
|
||||||
|
- $type: hw.view.group.HGroupView
|
||||||
|
geometry.width: 100%
|
||||||
|
views:
|
||||||
|
- $type: hw.view.form.ButtonView
|
||||||
|
text: Upload
|
||||||
|
+onPress: ~upload()
|
||||||
- $type: hw.view.form.LabelView
|
- $type: hw.view.form.LabelView
|
||||||
text: $r:text:app.version
|
text: $r:text:app.version
|
||||||
geometry.position: absolute
|
geometry.position: absolute
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import ru.m.puzzlez.proto.game.ImageId;
|
|||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
loading = new LoadingWrapper(imagesView);
|
loading = new LoadingWrapper(imagesView);
|
||||||
page = {index: 0, count: 6};
|
page = {index: 0, count: 6, order: [{field: "date", reverse: true}]};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function pageFactory(index:Int, value:Int):ToggleButtonView {
|
private function pageFactory(index:Int, value:Int):ToggleButtonView {
|
||||||
|
|||||||
176
src/app/haxe/ru/m/storage/SharedObjectDataStorage.hx
Normal file
176
src/app/haxe/ru/m/storage/SharedObjectDataStorage.hx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
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<String, Dynamic>;
|
||||||
|
|
||||||
|
typedef IdMeta<I> = {
|
||||||
|
var id:I;
|
||||||
|
var meta:DataMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataHelper<D, I> {
|
||||||
|
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<Bytes, String> {
|
||||||
|
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<D, I> implements DataStorage<D, I> {
|
||||||
|
private static var DATA_KEY:String = "data";
|
||||||
|
|
||||||
|
public var name(default, null):String;
|
||||||
|
public var version(default, null):Int;
|
||||||
|
|
||||||
|
private var helper:DataHelper<D, I>;
|
||||||
|
|
||||||
|
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<D, I>, 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<Filter>, 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<IdMeta<I>>):Void {
|
||||||
|
if (order != null) {
|
||||||
|
values.sort((a:IdMeta<I>, b:IdMeta<I>) -> {
|
||||||
|
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<DataPage<I>> {
|
||||||
|
var data = indexObject.data;
|
||||||
|
var values:Array<IdMeta<I>> = [];
|
||||||
|
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<I> = 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<DataPage<D>> {
|
||||||
|
return getIndexPage(page).pipe((indexPage:DataPage<I>) -> {
|
||||||
|
return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> {
|
||||||
|
return {
|
||||||
|
page: indexPage.page,
|
||||||
|
total: indexPage.total,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(id:I):Promise<D> {
|
||||||
|
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<D> {
|
||||||
|
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<Bool> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user