com.money.manager.ex.investment.morningstar.MorningstarPriceUpdater.java Source code

Java tutorial

Introduction

Here is the source code for com.money.manager.ex.investment.morningstar.MorningstarPriceUpdater.java

Source

/*
 * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team
 *
 * 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 com.money.manager.ex.investment.morningstar;

import android.content.Context;
import android.text.TextUtils;

import com.money.manager.ex.MoneyManagerApplication;
import com.money.manager.ex.R;
import com.money.manager.ex.core.UIHelper;
import com.money.manager.ex.datalayer.StockHistoryRepositorySql;
import com.money.manager.ex.datalayer.StockRepositorySql;
import com.money.manager.ex.investment.ISecurityPriceUpdater;
import com.money.manager.ex.investment.PriceUpdaterBase;
import com.money.manager.ex.investment.events.AllPricesDownloadedEvent;
import com.money.manager.ex.investment.events.PriceDownloadedEvent;
import com.money.manager.ex.utils.MmxDate;
import com.money.manager.ex.utils.MmxDateTimeUtils;
import com.squareup.sqlbrite.BriteDatabase;

import org.apache.commons.lang3.tuple.Pair;
import org.greenrobot.eventbus.EventBus;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.util.Date;
import java.util.List;

import javax.inject.Inject;

import dagger.Lazy;
import info.javaperformance.money.Money;
import info.javaperformance.money.MoneyFactory;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;

/**
 * Quote provider: Morningstar
 */
public class MorningstarPriceUpdater extends PriceUpdaterBase implements ISecurityPriceUpdater {

    @Inject
    public MorningstarPriceUpdater(Context context) {
        super(context);

        MoneyManagerApplication.getApp().iocComponent.inject(this);
    }

    /**
     * Tracks the number of records to update. Used to close progress binaryDialog when all done.
     */
    private int mCounter;
    private int mTotalRecords;
    private CompositeSubscription compositeSubscription;
    @Inject
    Lazy<StockRepositorySql> stockRepository;
    @Inject
    Lazy<StockHistoryRepositorySql> stockHistoryRepository;
    private SymbolConverter symbolConverter;
    private IMorningstarService service;

    @Override
    public void downloadPrices(List<String> symbols) {
        if (symbols == null || symbols.isEmpty())
            return;
        mTotalRecords = symbols.size();
        if (mTotalRecords == 0)
            return;

        showProgressDialog(mTotalRecords);

        service = getMorningstarService();
        compositeSubscription = new CompositeSubscription();
        symbolConverter = new SymbolConverter();

        //        processSequentially(symbols);
        processInParallel(symbols);
    }

    private void processSequentially(List<String> symbols) {
        compositeSubscription
                .add(Observable.from(symbols).subscribeOn(Schedulers.io()).map(new Func1<String, String>() {
                    @Override
                    public String call(String s) {
                        // get a Morningstar symbol
                        return symbolConverter.convert(s);
                    }
                }).observeOn(Schedulers.io()) // Observe the network call on IO thread!
                        .flatMap(new Func1<String, Observable<Pair<String, String>>>() {
                            @Override
                            public Observable<Pair<String, String>> call(final String s) {
                                // download the price
                                return service.getPrice(s).doOnError(new Action1<Throwable>() {
                                    @Override
                                    public void call(Throwable throwable) {
                                        mCounter++;
                                        setProgress(mCounter);

                                        // report to the UI
                                        Timber.e(throwable, "fetching %s", s);
                                    }
                                }).onErrorResumeNext(Observable.<String>empty())
                                        .map(new Func1<String, Pair<String, String>>() {
                                            @Override
                                            public Pair<String, String> call(String price) {
                                                return Pair.of(s, price);
                                            }
                                        });
                            }
                        }).map(new Func1<Pair<String, String>, PriceDownloadedEvent>() {
                            @Override
                            public PriceDownloadedEvent call(Pair<String, String> s) {
                                // parse the price
                                return parse(s.getLeft(), s.getRight());
                            }
                        }).doOnNext(new Action1<PriceDownloadedEvent>() {
                            @Override
                            public void call(PriceDownloadedEvent priceDownloadedEvent) {
                                // update to database
                                savePrice(priceDownloadedEvent);
                            }
                        }).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Subscriber<PriceDownloadedEvent>() {
                            @Override
                            public void onCompleted() {
                                closeProgressDialog();

                                new UIHelper(getContext()).showToast(R.string.download_complete);

                                compositeSubscription.unsubscribe();

                                // send event to reload the data.
                                EventBus.getDefault().post(new AllPricesDownloadedEvent());
                            }

                            @Override
                            public void onError(Throwable e) {
                                closeProgressDialog();

                                Timber.e(e, "downloading prices");
                            }

                            @Override
                            public void onNext(PriceDownloadedEvent event) {
                                mCounter++;
                                setProgress(mCounter);

                                // todo: update price in the UI?
                                // todo: remove the progress bar in that case.
                            }
                        }));
    }

    private void processInParallel(List<String> symbols) {
        for (int i = 0; i < symbols.size(); i++) {
            final String symbol = symbols.get(i);
            final String morningstarSymbol = symbolConverter.convert(symbol);

            compositeSubscription.add(service.getPrice(morningstarSymbol).subscribeOn(Schedulers.io())
                    .doOnNext(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            PriceDownloadedEvent event = parse(morningstarSymbol, s);
                            savePrice(event);
                        }
                    }).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<String>() {
                        @Override
                        public void onCompleted() {
                            finishIfAllDone();
                        }

                        @Override
                        public void onError(Throwable e) {
                            mCounter++;
                            setProgress(mCounter);

                            Timber.e(e, "error downloading price %s", symbol);

                            finishIfAllDone();
                        }

                        @Override
                        public void onNext(String s) {
                            mCounter++;
                            setProgress(mCounter);
                        }
                    }));
        }
        // unsubscribe if the user navigates away while downloading prices?
    }

    /**
     * Parse Morningstar response into price information.
     * @param symbol Morningstar symbol
     * @param html Result
     * @return An object containing price details
     */
    private PriceDownloadedEvent parse(String symbol, String html) {
        Document doc = Jsoup.parse(html);

        // symbol
        String yahooSymbol = symbolConverter.getYahooSymbol(symbol);

        // price
        String priceString = doc.body().getElementById("last-price-value").text();
        if (TextUtils.isEmpty(priceString)) {
            throw new RuntimeException("No price available for " + symbol);
        }
        Money price = MoneyFactory.fromString(priceString);
        // currency
        String currency = doc.body().getElementById("curency").text();
        if (currency.equals("GBX")) {
            price = price.divide(100, MoneyFactory.MAX_ALLOWED_PRECISION);
        }

        // date
        String dateString = doc.body().getElementById("asOfDate").text();
        String dateFormat = "MM/dd/yyyy HH:mm:ss";
        //        DateTimeFormatter formatter = DateTimeFormat.forPattern(dateFormat);
        // the time zone is EST
        //        DateTime date = formatter.withZone(DateTimeZone.forID("America/New_York"))
        //                .parseDateTime(dateString)
        //                .withZone(DateTimeZone.forID("Europe/Vienna"));
        // convert time zone
        MmxDate dateTime = new MmxDate(dateString, dateFormat).setTimeZone("America/New_York")
                .inTimeZone("Europe/Vienna");

        // todo: should this be converted to the exchange time?

        return new PriceDownloadedEvent(yahooSymbol, price, dateTime.toDate());
    }

    private synchronized void finishIfAllDone() {
        if (mCounter != mTotalRecords)
            return;

        compositeSubscription.unsubscribe();

        closeProgressDialog();

        // Notify user that all the prices have been downloaded.
        new UIHelper(getContext()).showToast(R.string.download_complete);

        // fire an event so that the data can be reloaded.
        EventBus.getDefault().post(new AllPricesDownloadedEvent());
    }

    private void savePrice(PriceDownloadedEvent event) {
        BriteDatabase.Transaction tx = stockRepository.get().database.newTransaction();

        // update the current price of the stock.
        stockRepository.get().updateCurrentPrice(event.symbol, event.price);

        // update price history record.
        stockHistoryRepository.get().addStockHistoryRecord(event.symbol, event.price, event.date);

        tx.markSuccessful();
        tx.end();
    }

    private IMorningstarService getMorningstarService() {
        String BASE_URL = "http://quotes.morningstar.com";

        Retrofit retrofit = new Retrofit.Builder().addConverterFactory(ScalarsConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()).baseUrl(BASE_URL).build();
        return retrofit.create(IMorningstarService.class);
    }
}