fr.pasteque.client.sync.SyncUpdate.java Source code

Java tutorial

Introduction

Here is the source code for fr.pasteque.client.sync.SyncUpdate.java

Source

/*
 Pasteque Android client
 Copyright (C) Pasteque contributors, see the COPYRIGHT file
    
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package fr.pasteque.client.sync;

import android.content.Context;
import android.os.Message;
import android.os.Handler;
import android.util.Log;

import java.io.IOError;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;

import fr.pasteque.client.data.Data;
import fr.pasteque.client.models.*;
import fr.pasteque.client.utils.exception.DataCorruptedException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import fr.pasteque.client.Configure;
import fr.pasteque.client.data.ImagesData;
import fr.pasteque.client.data.DataSavable.StockData;
import fr.pasteque.client.utils.URLTextGetter;

import java.text.ParseException;

/**
 * Some request need an order. Parsers call the next request We think that this
 * implementation can be improved..
 *
 * Here is the request life cycle Version
 * '-> CashRegister
 * '-> TAX -> CATEGORY -> PRODUCT -> COMPOSITION
 * '-> ROLE -> USER
 * '-> CUSTOMER
 * '-> CASH
 * '-> TARIFF
 * '-> PAYMENTMODE
 * '-> RESSOURCES
 * '-> PLACES
 * '-> LOCATION -> STOCK
 * '-> DISCOUNT
 *
 * @author nsvir
 */
public class SyncUpdate {

    private static final String LOG_TAG = "Pasteque/SyncUpdate";

    // Note: SyncUpdate uses positive values, SyncSend negative ones
    public static final int SYNC_DONE = 1;
    public static final int CONNECTION_FAILED = 2;
    public static final int CATALOG_SYNC_DONE = 3;
    public static final int USERS_SYNC_DONE = 4;
    public static final int CATEGORIES_SYNC_DONE = 5;
    public static final int CASH_SYNC_DONE = 6;
    public static final int PLACES_SYNC_DONE = 7;
    public static final int SYNC_ERROR = 8;
    public static final int CUSTOMERS_SYNC_DONE = 9;
    public static final int STOCK_SYNC_DONE = 10;
    public static final int COMPOSITIONS_SYNC_DONE = 11;
    public static final int TARIFF_AREAS_SYNC_DONE = 12;
    public static final int STOCK_SYNC_ERROR = 13;
    public static final int CATEGORIES_SYNC_ERROR = 14;
    public static final int CATALOG_SYNC_ERROR = 15;
    public static final int USERS_SYNC_ERROR = 16;
    public static final int CUSTOMERS_SYNC_ERROR = 17;
    public static final int CASH_SYNC_ERROR = 18;
    public static final int PLACES_SYNC_ERROR = 19;
    public static final int COMPOSITIONS_SYNC_ERROR = 20;
    public static final int TARIFF_AREA_SYNC_ERROR = 21;
    public static final int INCOMPATIBLE_VERSION = 22;
    public static final int ROLES_SYNC_DONE = 23;
    public static final int ROLES_SYNC_ERROR = 24;
    public static final int TAXES_SYNC_DONE = 25;
    public static final int TAXES_SYNC_ERROR = 26;
    public static final int LOCATIONS_SYNC_DONE = 27;
    public static final int LOCATIONS_SYNC_ERROR = 28;
    public static final int VERSION_DONE = 29;
    public static final int STOCKS_SKIPPED = 30;
    public static final int PLACES_SKIPPED = 31;
    public static final int CASHREG_SYNC_DONE = 32;
    public static final int CASHREG_SYNC_ERROR = 33;
    public static final int CASHREG_SYNC_NOTFOUND = 34;
    public static final int RESOURCE_SYNC_DONE = 35;
    public static final int RESOURCE_SYNC_ERROR = 36;
    public static final int PAYMENTMODE_SYNC_DONE = 37;
    public static final int PAYMENTMODE_SYNC_ERROR = 38;
    public static final int DISCOUNT_SYNC_DONE = 39;
    public static final int DISCOUNT_SYNC_ERROR = 40;
    public static final int SYNC_ERROR_NOT_LOGGED = 41;

    private static final String[] resToLoad = new String[] { "MobilePrinter.Header", "MobilePrinter.Footer",
            "MobilePrinter.Logo" };

    private Context ctx;
    private Handler listener;
    private boolean versionDone;
    private boolean discountDone;
    private boolean cashRegDone;
    private boolean taxesDone;
    private boolean categoriesDone;
    private boolean productsDone;
    private boolean rolesDone;
    private boolean usersDone;
    private boolean customersDone;
    private boolean cashDone;
    private boolean placesDone;
    private boolean locationsDone;
    private boolean stocksDone;
    private boolean compositionsDone;
    private boolean tariffAreasDone;
    private boolean paymentModesDone;
    private int resLoaded;
    private boolean isANewUserAccount;
    public static final int STEPS = 16 + resToLoad.length;
    /**
     * Stop parallel messages in case of error
     */
    private boolean stop;

    /**
     * The catalog to build with multiple syncs
     */
    private Catalog catalog;
    /**
     * Categories by id for quick products assignment
     */
    private Map<String, Category> categories;
    /**
     * Permissions by role id
     */
    private Map<String, String> permissions;
    /**
     * Tax rates by tax cat id
     */
    private Map<String, Double> taxRates;
    /**
     * Tax ids by tax cat id
     */
    private Map<String, String> taxIds;
    private int cashRegId;

    public SyncUpdate(Context ctx, Handler listener) {
        this.listener = listener;
        this.ctx = ctx;
        this.isANewUserAccount = !Data.Login.equalsConfiguredAccount(this.ctx);
        this.catalog = new Catalog();
        this.categories = new HashMap<String, Category>();
        this.permissions = new HashMap<String, String>();
        this.taxRates = new HashMap<String, Double>();
        this.taxIds = new HashMap<String, String>();
    }

    /**
     * Launch synchronization
     */
    public void startSyncUpdate() {
        synchronize();
    }

    public void synchronize() {
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx), SyncUtils.initParams(this.ctx, "VersionAPI", "get"),
                new DataHandler(DataHandler.TYPE_VERSION));
    }

    private void parseVersion(JSONObject resp) {
        try {
            JSONObject o = resp.getJSONObject("content");
            String version = o.getString("version");
            String level = o.getString("level");
            Version.setVersion(version, level);
            if (!Version.isValid(this.ctx)) {
                SyncUtils.notifyListener(this.listener, INCOMPATIBLE_VERSION);
            } else {
                SyncUtils.notifyListener(this.listener, VERSION_DONE);
                String baseUrl = SyncUtils.apiUrl(this.ctx);
                Map<String, String> cashParams = SyncUtils.initParams(this.ctx, "CashRegistersAPI", "get");
                cashParams.put("label", Configure.getMachineName(this.ctx));
                URLTextGetter.getText(baseUrl, cashParams, new DataHandler(DataHandler.TYPE_CASHREGISTER));
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, SYNC_ERROR, e);
        }
    }

    private void parseCashRegister(JSONObject resp) {
        CashRegister cashReg = null;
        try {
            if (resp.isNull("content")) {
                SyncUtils.notifyListener(this.listener, CASHREG_SYNC_NOTFOUND);
                return;
            }
            JSONObject o = resp.getJSONObject("content");
            cashReg = CashRegister.fromJSON(o);
            this.cashRegId = cashReg.getId();
            Data.TicketId.updateTicketId(cashReg, this.isANewUserAccount);
            Data.TicketId.save(this.ctx);
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, CASHREG_SYNC_ERROR, e);
            return;
        } catch (IOError e) {
            Log.d(LOG_TAG, "Could not save ticketId");
        }
        SyncUtils.notifyListener(this.listener, CASHREG_SYNC_DONE, cashReg);

        // Continue sync
        String baseUrl = SyncUtils.apiUrl(this.ctx);
        Map<String, String> cashParams = SyncUtils.initParams(this.ctx, "CashesAPI", "get");
        cashParams.put("cashRegisterId", String.valueOf(cashReg.getId()));
        URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "TaxesAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_TAX));
        URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "RolesAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_ROLE));
        URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "CustomersAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_CUSTOMERS));
        URLTextGetter.getText(baseUrl, cashParams, new DataHandler(DataHandler.TYPE_CASH));
        URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "TariffAreasAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_TARIFF));
        URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "PaymentModesAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_PAYMENTMODE));
        for (String res : resToLoad) {
            Map<String, String> resParams = SyncUtils.initParams(this.ctx, "ResourcesAPI", "get");
            resParams.put("label", res);
            URLTextGetter.getText(baseUrl, resParams, new DataHandler(DataHandler.TYPE_RESOURCE));
        }
        if (Configure.getTicketsMode(this.ctx) == Configure.RESTAURANT_MODE) {
            // Restaurant mode: get places
            URLTextGetter.getText(baseUrl, SyncUtils.initParams(this.ctx, "PlacesAPI", "getAll"),
                    new DataHandler(DataHandler.TYPE_PLACES));
        } else {
            // Other mode: skip places
            placesDone = true;
            SyncUtils.notifyListener(this.listener, PLACES_SKIPPED);
        }
        // Stock management: get stocks
        Map<String, String> locParams = SyncUtils.initParams(this.ctx, "LocationsAPI", "get");
        locParams.put("id", cashReg.getLocationId());
        URLTextGetter.getText(baseUrl, locParams, new DataHandler(DataHandler.TYPE_LOCATION));

        Map<String, String> discountParams = SyncUtils.initParams(this.ctx, "DiscountsAPI", "getAll");
        URLTextGetter.getText(baseUrl, discountParams, new DataHandler(DataHandler.TYPE_DISCOUNT)); //TODO change API to 6

    }

    private void parseTaxes(JSONObject resp) {
        try {
            JSONArray array = resp.getJSONArray("content");
            for (int i = 0; i < array.length(); i++) {
                JSONObject taxCat = array.getJSONObject(i);
                String taxCatId = taxCat.getString("id");
                JSONArray taxes = taxCat.getJSONArray("taxes");
                long now = System.currentTimeMillis() / 1000;
                int index = 0;
                long maxDate = 0;
                for (int j = 0; j < taxes.length(); j++) {
                    JSONObject tax = taxes.getJSONObject(j);
                    long date = tax.getLong("startDate");
                    if (date > maxDate && date < now) {
                        index = j;
                        maxDate = date;
                    }
                }
                JSONObject currentTax = taxes.getJSONObject(index);
                double rate = currentTax.getDouble("rate");
                String id = currentTax.getString("id");
                this.taxRates.put(taxCatId, rate);
                this.taxIds.put(taxCatId, id);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, TAXES_SYNC_ERROR, e);
            this.taxesDone = true;
            this.productsDone = true;
            this.compositionsDone = true;
            return;
        }
        SyncUtils.notifyListener(this.listener, TAXES_SYNC_DONE, this.taxRates);
        // Start synchronizing catalog
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx), SyncUtils.initParams(this.ctx, "CategoriesAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_CATEGORY));
    }

    /**
     * Parse categories and start products sync to create catalog
     */
    private void parseCategories(JSONObject resp) {
        Map<String, List<Category>> children = new HashMap<String, List<Category>>();
        try {
            JSONArray array = resp.getJSONArray("content");
            // First pass: read all and register parents
            for (int i = 0; i < array.length(); i++) {
                JSONObject o = array.getJSONObject(i);
                Category c = Category.fromJSON(o);
                String parent = null;
                if (!o.isNull("parent_id")) {
                    parent = o.getString("parent_id");
                }
                if (!children.containsKey(parent)) {
                    children.put(parent, new ArrayList<Category>());
                }
                children.get(parent).add(c);
                this.categories.put(c.getId(), c);
            }
            // Second pass: build subcategories
            for (Category root : children.get(null)) {
                // Build subcategories
                this.parseSubcats(root, children);
                // This branch is ready, add to catalog
                this.catalog.addRootCategory(root);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, CATEGORIES_SYNC_ERROR, e);
            this.productsDone = true;
            this.compositionsDone = true;
            return;
        }
        SyncUtils.notifyListener(this.listener, CATEGORIES_SYNC_DONE, children.get(null));
        // Start synchronizing products
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx), SyncUtils.initParams(this.ctx, "ProductsAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_PRODUCT));
    }

    // recursive subroutine of parseCategories
    private void parseSubcats(Category c, Map<String, List<Category>> children) {
        if (children.containsKey(c.getId())) {
            for (Category sub : children.get(c.getId())) {
                c.addSubcategory(sub);
                this.parseSubcats(sub, children);
            }
        }
    }

    private void parseProducts(JSONObject resp) {
        try {
            JSONArray array = resp.getJSONArray("content");
            try {
                ImagesData.clearProducts(this.ctx);
            } catch (IOException e) {
                Log.w(LOG_TAG, "Unable to clear product images", e);
            }
            for (int i = 0; i < array.length(); i++) {
                JSONObject o = array.getJSONObject(i);
                String taxCatId = o.getString("taxCatId");
                String taxId = this.taxIds.get(taxCatId);
                double taxRate = this.taxRates.get(taxCatId);
                Product p = Product.fromJSON(o, taxId, taxRate);
                if (o.getBoolean("hasImage")) {
                    // TODO: call for image
                    /*String image64 = o.getString("image");
                     try {
                     byte[] data = Base64.decode(image64);
                     ImagesData.storeProductImage(this.ctx, p.getId(), data);
                     } catch (IOException e) {
                     Log.w(LOG_TAG, "Unable to read product image for "
                     + p.getId(), e);
                     }*/
                }
                // Find its category and add it
                if (o.getBoolean("visible")) {
                    String catId = o.getString("categoryId");
                    for (Category c : this.catalog.getAllCategories()) {
                        if (c.getId().equals(catId)) {
                            this.catalog.addProduct(c, p);
                            break;
                        }
                    }
                } else {
                    this.catalog.addProduct(p);
                }
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, CATALOG_SYNC_ERROR, e);
            this.compositionsDone = true;
            return;
        }
        SyncUtils.notifyListener(this.listener, CATALOG_SYNC_DONE, this.catalog);
        // Start synchronizing compositions
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx),
                SyncUtils.initParams(this.ctx, "CompositionsAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_COMPOSITION));
    }

    private void parseRoles(JSONObject resp) {
        try {
            JSONArray array = resp.getJSONArray("content");
            for (int i = 0; i < array.length(); i++) {
                JSONObject o = array.getJSONObject(i);
                String id = o.getString("id");
                String permissions = o.getString("permissions");
                this.permissions.put(id, permissions);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, ROLES_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, ROLES_SYNC_DONE, this.permissions);
        // Start synchronizing users
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx), SyncUtils.initParams(this.ctx, "UsersAPI", "getAll"),
                new DataHandler(DataHandler.TYPE_USER));
    }

    /**
     * Parse users from JSONObject response. Roles must be parsed.
     */
    private void parseUsers(JSONObject resp) {
        List<User> users = new ArrayList<User>();
        try {
            JSONArray array = resp.getJSONArray("content");
            for (int i = 0; i < array.length(); i++) {
                JSONObject o = array.getJSONObject(i);
                String roleId = o.getString("roleId");
                String permissions = this.permissions.get(roleId);
                User u = User.fromJSON(o, permissions);
                users.add(u);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, USERS_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, USERS_SYNC_DONE, users);
    }

    private void parseCustomers(JSONObject resp) {
        List<Customer> customers = new ArrayList<Customer>();
        try {
            JSONArray array = resp.getJSONArray("content");
            for (int i = 0; i < array.length(); i++) {
                JSONObject o = array.getJSONObject(i);
                Customer c = Customer.fromJSON(o);
                customers.add(c);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, CUSTOMERS_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, CUSTOMERS_SYNC_DONE, customers);
    }

    private void parseCash(JSONObject resp) {
        Cash cash = null;
        try {
            if (resp.isNull("content")) {
                cash = new Cash(this.cashRegId);
            } else {
                JSONObject o = resp.getJSONObject("content");
                cash = Cash.fromJSON(o);
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Unable to parse response: " + resp.toString(), e);
            SyncUtils.notifyListener(this.listener, CASH_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, CASH_SYNC_DONE, cash);
    }

    private void parsePlaces(JSONObject resp) {
        List<Floor> floors = new ArrayList<Floor>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                Floor f = Floor.fromJSON(o);
                floors.add(f);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, PLACES_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, PLACES_SYNC_DONE, floors);
    }

    private void parseLocation(JSONObject resp) {
        String locationId = null;
        String location = null;
        try {
            JSONObject o = resp.getJSONObject("content");
            locationId = o.getString("id");
            location = o.getString("label");
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, LOCATIONS_SYNC_ERROR, e);
            this.stocksDone = true;
            return;
        }
        // Save id
        try {
            StockData.saveLocation(this.ctx, location, locationId);
        } catch (IOException e) {
            // Should not happen but it will screw up stocks. Make it fail
            SyncUtils.notifyListener(this.listener, LOCATIONS_SYNC_ERROR, e);
            this.stocksDone = true;
            return;
        }
        // Notify success
        SyncUtils.notifyListener(this.listener, LOCATIONS_SYNC_DONE, locationId);
        // Start synchronizing stocks
        Map<String, String> stockParams = SyncUtils.initParams(this.ctx, "StocksAPI", "getAll");
        stockParams.put("locationId", locationId);
        URLTextGetter.getText(SyncUtils.apiUrl(this.ctx), stockParams, new DataHandler(DataHandler.TYPE_STOCK));
    }

    private void parseStocks(JSONObject resp) {
        Map<String, Stock> stocks = new HashMap<String, Stock>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                Stock s = Stock.fromJSON(o);
                stocks.put(s.getProductId(), s);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, STOCK_SYNC_ERROR, e);
        }
        SyncUtils.notifyListener(this.listener, STOCK_SYNC_DONE, stocks);
    }

    private void parseCompositions(JSONObject resp) {
        Map<String, Composition> compos = new HashMap<String, Composition>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                Composition c = Composition.fromJSON(o);
                compos.put(c.getProductId(), c);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, COMPOSITIONS_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, COMPOSITIONS_SYNC_DONE, compos);
    }

    private void parseTariffAreas(JSONObject resp) {
        List<TariffArea> areas = new ArrayList<TariffArea>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                TariffArea area = TariffArea.fromJSON(o);
                areas.add(area);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, TARIFF_AREA_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, TARIFF_AREAS_SYNC_DONE, areas);
    }

    private void parsePaymentModes(JSONObject resp) {
        List<PaymentMode> modes = new ArrayList<PaymentMode>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                PaymentMode mode = PaymentMode.fromJSON(o);
                modes.add(mode);
            }
            Collections.sort(modes, new Comparator<PaymentMode>() {
                @Override
                public int compare(PaymentMode o1, PaymentMode o2) {
                    return o1.getDispOrder() - o2.getDispOrder();
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, PAYMENTMODE_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, PAYMENTMODE_SYNC_DONE, modes);

    }

    private void parseResource(JSONObject resp) {
        try {
            if (resp.isNull("content")) {
                SyncUtils.notifyListener(this.listener, RESOURCE_SYNC_DONE, null);
                return;
            }
            JSONObject res = resp.getJSONObject("content");
            String resContent = res.getString("content");
            String name = res.getString("label");
            SyncUtils.notifyListener(this.listener, RESOURCE_SYNC_DONE, new String[] { name, resContent });
        } catch (JSONException e) {
            e.printStackTrace();
            SyncUtils.notifyListener(this.listener, RESOURCE_SYNC_ERROR, e);
        }
    }

    private void parseDiscount(JSONObject resp) {
        ArrayList<Discount> discounts = new ArrayList<>();
        try {
            JSONArray a = resp.getJSONArray("content");
            for (int i = 0; i < a.length(); i++) {
                JSONObject o = a.getJSONObject(i);
                Discount disc = Discount.fromJSON(o);
                discounts.add(disc);
            }

        } catch (JSONException | ParseException e) {
            SyncUtils.notifyListener(this.listener, DISCOUNT_SYNC_ERROR, e);
            return;
        }
        SyncUtils.notifyListener(this.listener, DISCOUNT_SYNC_DONE, discounts);
    }

    private void finish() {
        SyncUtils.notifyListener(this.listener, SYNC_DONE);
    }

    private void checkFinished() {
        if (this.categoriesDone && this.productsDone && this.rolesDone && this.usersDone && this.cashDone
                && this.placesDone && this.stocksDone && this.compositionsDone && this.taxesDone
                && this.tariffAreasDone && this.versionDone && this.customersDone && this.locationsDone
                && this.cashRegDone && this.paymentModesDone && this.resLoaded == resToLoad.length) {
            this.finish();
        }
    }

    private class DataHandler extends Handler {

        private static final int TYPE_USER = 1;
        private static final int TYPE_PRODUCT = 2;
        private static final int TYPE_CATEGORY = 3;
        private static final int TYPE_CASH = 4;
        private static final int TYPE_PLACES = 5;
        private static final int TYPE_CUSTOMERS = 6;
        private static final int TYPE_STOCK = 7;
        private static final int TYPE_COMPOSITION = 8;
        private static final int TYPE_TARIFF = 9;
        private static final int TYPE_VERSION = 10;
        private static final int TYPE_ROLE = 11;
        private static final int TYPE_TAX = 12;
        private static final int TYPE_LOCATION = 13;
        private static final int TYPE_CASHREGISTER = 14;
        private static final int TYPE_RESOURCE = 15;
        private static final int TYPE_PAYMENTMODE = 16;
        private static final int TYPE_DISCOUNT = 17;

        private int type;

        public DataHandler(int type) {
            this.type = type;
        }

        private String getError(String response) {
            try {
                JSONObject o = new JSONObject(response);
                if (o.has("error")) {
                    return o.getString("error");
                }
            } catch (JSONException ignored) {
            }
            return null;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (this.type) {
            case TYPE_VERSION:
                SyncUpdate.this.versionDone = true;
                break;
            case TYPE_CASHREGISTER:
                SyncUpdate.this.cashRegDone = true;
                break;
            case TYPE_USER:
                SyncUpdate.this.usersDone = true;
                break;
            case TYPE_TAX:
                SyncUpdate.this.taxesDone = true;
                break;
            case TYPE_PRODUCT:
                SyncUpdate.this.productsDone = true;
                break;
            case TYPE_CATEGORY:
                SyncUpdate.this.categoriesDone = true;
                break;
            case TYPE_CASH:
                SyncUpdate.this.cashDone = true;
                break;
            case TYPE_PLACES:
                SyncUpdate.this.placesDone = true;
                break;
            case TYPE_ROLE:
                SyncUpdate.this.rolesDone = true;
                break;
            case TYPE_CUSTOMERS:
                SyncUpdate.this.customersDone = true;
                break;
            case TYPE_LOCATION:
                SyncUpdate.this.locationsDone = true;
                break;
            case TYPE_STOCK:
                SyncUpdate.this.stocksDone = true;
                break;
            case TYPE_COMPOSITION:
                SyncUpdate.this.compositionsDone = true;
                break;
            case TYPE_TARIFF:
                SyncUpdate.this.tariffAreasDone = true;
                break;
            case TYPE_PAYMENTMODE:
                SyncUpdate.this.paymentModesDone = true;
                break;
            case TYPE_DISCOUNT:
                SyncUpdate.this.discountDone = true;
                break;
            case TYPE_RESOURCE:
                SyncUpdate.this.resLoaded++;
                break;
            }
            switch (msg.what) {
            case URLTextGetter.SUCCESS:
                // Parse content
                String content = (String) msg.obj;
                try {
                    JSONObject result = new JSONObject(content);
                    String status = result.getString("status");
                    if (!status.equals("ok")) {
                        JSONObject err = result.getJSONObject("content");
                        String error = err.getString("code");
                        if (listener != null && !stop) {
                            Log.e(LOG_TAG, "Unable to parse response " + content);
                            SyncUtils.notifyListener(listener, SYNC_ERROR, error);
                        }
                        stop = true;
                        finish();
                    } else if (!stop) {
                        switch (type) {
                        case TYPE_VERSION:
                            parseVersion(result);
                            break;
                        case TYPE_CASHREGISTER:
                            parseCashRegister(result);
                            break;
                        case TYPE_ROLE:
                            parseRoles(result);
                            break;
                        case TYPE_USER:
                            parseUsers(result);
                            break;
                        case TYPE_TAX:
                            parseTaxes(result);
                            break;
                        case TYPE_PRODUCT:
                            parseProducts(result);
                            break;
                        case TYPE_CATEGORY:
                            parseCategories(result);
                            break;
                        case TYPE_CASH:
                            parseCash(result);
                            break;
                        case TYPE_PLACES:
                            parsePlaces(result);
                            break;
                        case TYPE_CUSTOMERS:
                            parseCustomers(result);
                            break;
                        case TYPE_LOCATION:
                            parseLocation(result);
                            break;
                        case TYPE_STOCK:
                            parseStocks(result);
                            break;
                        case TYPE_COMPOSITION:
                            parseCompositions(result);
                            break;
                        case TYPE_TARIFF:
                            parseTariffAreas(result);
                            break;
                        case TYPE_PAYMENTMODE:
                            parsePaymentModes(result);
                            break;
                        case TYPE_DISCOUNT:
                            parseDiscount(result);
                            break;
                        case TYPE_RESOURCE:
                            parseResource(result);
                        }
                    }
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "Unable to parse response " + content);
                    if (!stop) {
                        SyncUtils.notifyListener(listener, SYNC_ERROR, e);
                    }
                    stop = true;
                    finish();
                }
                break;
            case URLTextGetter.ERROR:

                ((Exception) msg.obj).printStackTrace();
            case URLTextGetter.STATUS_NOK:
                if (!stop) {
                    SyncUtils.notifyListener(listener, CONNECTION_FAILED, msg.obj);
                }
                stop = true;
                finish();
                return;
            }
            checkFinished();
        }
    }

}