com.freshdigitable.udonroad.module.realm.TimelineStoreRealm.java Source code

Java tutorial

Introduction

Here is the source code for com.freshdigitable.udonroad.module.realm.TimelineStoreRealm.java

Source

/*
 * Copyright (c) 2016. Matsuda, Akihit (akihito104)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.freshdigitable.udonroad.module.realm;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.freshdigitable.udonroad.datastore.ConfigStore;
import com.freshdigitable.udonroad.datastore.TypedCache;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmResults;
import io.realm.Sort;
import rx.Observable;
import rx.functions.Action1;
import twitter4j.Status;

import static com.freshdigitable.udonroad.module.realm.StatusRealm.KEY_ID;
import static com.freshdigitable.udonroad.module.realm.StatusRealm.KEY_QUOTAD_STATUS_ID;
import static com.freshdigitable.udonroad.module.realm.StatusRealm.KEY_RETWEETED_STATUS_ID;

/**
 * TimelineStoreRealm implements TimelineStore for Realm.
 *
 * Created by akihit on 2016/07/23.
 */
public class TimelineStoreRealm extends BaseSortedCacheRealm<Status> {
    private static final String TAG = TimelineStoreRealm.class.getSimpleName();
    private RealmResults<StatusIDs> timeline;
    private final TypedCache<Status> statusCache;
    private final ConfigStore configStore;

    public TimelineStoreRealm(TypedCache<Status> statusCacheRealm, ConfigStore configStore) {
        this.statusCache = statusCacheRealm;
        this.configStore = configStore;
    }

    @Override
    public void open(String storeName) {
        super.open(storeName);
        statusCache.open();
        configStore.open();
        defaultTimeline();
    }

    private void defaultTimeline() {
        timeline = realm.where(StatusIDs.class).findAllSorted(KEY_ID, Sort.DESCENDING);
    }

    @Override
    public void upsert(Status status) {
        if (status == null || isIgnorable(status)) {
            return;
        }
        upsert(Collections.singletonList(status));
    }

    private boolean isIgnorable(Status status) {
        return configStore.isIgnoredUser(status.getUser().getId())
                || (status.isRetweet() && configStore.isIgnoredUser(status.getRetweetedStatus().getUser().getId()));
    }

    @Override
    public void upsert(final List<Status> statuses) {
        if (statuses == null) {
            return;
        }
        for (int i = statuses.size() - 1; i >= 0; i--) {
            final Status status = statuses.get(i);
            if (isIgnorable(status)) {
                statuses.remove(status);
            }
        }
        if (statuses.isEmpty()) {
            return;
        }

        statusCache.observeUpsert(statuses).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                final List<StatusIDs> inserts = createInsertList(statuses);
                if (!inserts.isEmpty()) {
                    insertStatus(inserts);
                }
                final List<StatusIDs> updates = createUpdateList(statuses);
                if (!updates.isEmpty()) {
                    notifyChanged(updates, timeline);
                }
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                Log.e(TAG, "upsert: ", throwable);
            }
        });
    }

    private List<StatusIDs> createInsertList(List<Status> statuses) {
        final List<StatusIDs> inserts = new ArrayList<>();
        for (Status s : statuses) {
            final StatusIDs update = findTimeline(s);
            if (update == null) {
                inserts.add(new StatusIDs(s));
            }
        }
        return inserts;
    }

    private void insertStatus(List<StatusIDs> inserts) {
        realm.beginTransaction();
        final List<StatusIDs> inserted = realm.copyToRealmOrUpdate(inserts);
        realm.commitTransaction();
        if (inserted.isEmpty() || !insertEvent.hasObservers()) {
            return;
        }
        timeline.addChangeListener(new RealmChangeListener<RealmResults<StatusIDs>>() {
            @Override
            public void onChange(RealmResults<StatusIDs> element) {
                setItemCount(element.size());
                notifyInserted(inserted, element);
                element.removeChangeListener(this);
            }
        });
    }

    private void notifyInserted(List<StatusIDs> inserted, RealmResults<StatusIDs> results) {
        if (inserted.isEmpty()) {
            return;
        }
        final List<Integer> index = searchTimeline(inserted, results);
        if (index.isEmpty()) {
            return;
        }
        Collections.sort(index);
        for (int i : index) {
            insertEvent.onNext(i);
        }
    }

    private List<StatusIDs> createUpdateList(List<Status> statuses) {
        if (!updateEvent.hasObservers()) {
            return Collections.emptyList();
        }

        final List<StatusIDs> updates = new ArrayList<>();
        for (Status s : statuses) {
            final StatusIDs update = findTimeline(s);
            if (update != null) {
                updates.add(update);
            }

            final RealmResults<StatusIDs> u = findReferringStatus(s.getId());
            if (!u.isEmpty()) {
                updates.addAll(u);
            }

            final long quotedStatusId = s.getQuotedStatusId();
            if (quotedStatusId > 0) {
                final Status quotedStatus = s.getQuotedStatus();

                final StatusIDs q = findTimeline(quotedStatus);
                if (q != null) {
                    updates.add(q);
                }

                final RealmResults<StatusIDs> updatedQuotedStatus = findReferringStatus(quotedStatusId);
                if (!updatedQuotedStatus.isEmpty()) {
                    updates.addAll(updatedQuotedStatus);
                }
            }

            if (!s.isRetweet()) {
                continue;
            }
            final Status retweetedStatus = s.getRetweetedStatus();

            final StatusIDs rs = findTimeline(retweetedStatus);
            if (rs != null) {
                updates.add(rs);
            }
            final RealmResults<StatusIDs> rtedUpdate = findReferringStatus(retweetedStatus.getId());
            if (!rtedUpdate.isEmpty()) {
                updates.addAll(rtedUpdate);
            }

            final long rtQuotedStatusId = retweetedStatus.getQuotedStatusId();
            if (rtQuotedStatusId > 0) {
                final RealmResults<StatusIDs> rtUpdatedQuotedStatus = findReferringStatus(rtQuotedStatusId);
                if (!rtUpdatedQuotedStatus.isEmpty()) {
                    updates.addAll(rtUpdatedQuotedStatus);
                }
            }
        }
        return updates;
    }

    private void notifyChanged(List<StatusIDs> changed, RealmResults<StatusIDs> results) {
        if (changed.isEmpty()) {
            return;
        }
        final List<Integer> index = searchTimeline(changed, results);
        if (index.isEmpty()) {
            return;
        }
        for (int i : index) {
            updateEvent.onNext(i);
        }
    }

    @Override
    public void insert(Status status) {
        statusCache.insert(status);
        final List<StatusIDs> updates = createUpdateList(Collections.singletonList(status));
        if (!updates.isEmpty()) {
            notifyChanged(updates, timeline);
        }
    }

    @NonNull
    private RealmResults<StatusIDs> findReferringStatus(long id) {
        return timeline.where().beginGroup().equalTo(KEY_QUOTAD_STATUS_ID, id).or()
                .equalTo(KEY_RETWEETED_STATUS_ID, id).endGroup().findAll();
    }

    @Nullable
    private StatusIDs findTimeline(Status newer) {
        if (newer == null) {
            return null;
        }
        return timeline.where().equalTo(KEY_ID, newer.getId()).findFirst();
    }

    @Override
    public void delete(long statusId) {
        final RealmResults<StatusIDs> res = realm.where(StatusIDs.class).beginGroup().equalTo(KEY_ID, statusId).or()
                .equalTo(KEY_RETWEETED_STATUS_ID, statusId).endGroup().findAll();
        if (res.isEmpty()) {
            return;
        }

        final List<Integer> deleted = searchTimeline(res);

        realm.beginTransaction();
        res.deleteAllFromRealm();
        realm.commitTransaction();

        if (deleted.isEmpty() || !deleteEvent.hasObservers()) {
            return;
        }
        timeline.addChangeListener(new RealmChangeListener<RealmResults<StatusIDs>>() {
            @Override
            public void onChange(RealmResults<StatusIDs> element) {
                Log.d(TAG, "call: deletedStatus");
                setItemCount(element.size());
                for (int d : deleted) {
                    deleteEvent.onNext(d);
                }
                for (StatusIDs ids : res) {
                    statusCache.delete(ids.getId());
                }
                element.removeChangeListener(this);
            }
        });
    }

    @NonNull
    private List<Integer> searchTimeline(List<StatusIDs> managedItems) {
        return searchTimeline(managedItems, timeline);
    }

    @NonNull
    private List<Integer> searchTimeline(List<StatusIDs> managedItems, RealmResults<StatusIDs> timeline) {
        final List<Integer> res = new ArrayList<>(managedItems.size());
        for (StatusIDs sr : managedItems) {
            final int index = timeline.indexOf(sr);
            if (index >= 0) {
                res.add(index);
            }
        }
        return res;
    }

    @Override
    public void clear() {
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.deleteAll();
            }
        });
    }

    @Override
    public void clearPool() {
        clear();
        statusCache.clear();
    }

    @Override
    public void close() {
        timeline.removeChangeListeners();
        super.close();
        statusCache.close();
        configStore.close();
    }

    @Override
    public Status get(int position) {
        final StatusIDs ids = timeline.get(position);
        return statusCache.find(ids.getId());
    }

    @Override
    public synchronized int getItemCount() {
        return itemCount;
    }

    private volatile int itemCount;

    private synchronized void setItemCount(int count) {
        itemCount = count;
    }

    @Override
    public long getLastPageCursor() {
        if (timeline.size() < 1) {
            return -1;
        }
        final StatusIDs lastStatus = timeline.last();
        return lastStatus.getId() - 1;
    }

    @Override
    public Status find(long statusId) {
        return statusCache.find(statusId);
    }

    @NonNull
    @Override
    public Observable<Status> observeById(long statusId) {
        return statusCache.observeById(statusId);
    }
}