All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mindsnacks.zinc.classes.data.ZincCatalogs Maven / Gradle / Ivy

There is a newer version: 1.6.2
Show newest version
package com.mindsnacks.zinc.classes.data;

import com.google.common.util.concurrent.*;
import com.mindsnacks.zinc.classes.ZincJobFactory;
import com.mindsnacks.zinc.classes.ZincLogging;
import com.mindsnacks.zinc.classes.fileutils.FileHelper;
import com.mindsnacks.zinc.exceptions.ZincRuntimeException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author Nacho Soto
 *
 * This class deals with persisting and scheduling downloading catalogs.
 */
public class ZincCatalogs implements ZincCatalogsCache {
    private static final long INITIAL_UPDATE_DELAY = TimeUnit.MINUTES.toMillis(5);
    private static final long UPDATE_FREQUENCY = TimeUnit.MINUTES.toMillis(5);

    private final File mRoot;
    private final FileHelper mFileHelper;

    private final Set mTrackedSourceURLs;

    private final ZincJobFactory mJobFactory;
    private final ListeningExecutorService mDownloadExecutorService;
    private final ExecutorService mPersistenceExecutorService;
    private final Timer mUpdateTimer;

    private final Map> mFutures = new HashMap>();
    private boolean mUpdateScheduled = false;

    public ZincCatalogs(final File root,
                        final FileHelper fileHelper,
                        final Set trackedSourceURLs,
                        final ZincJobFactory jobFactory,
                        final ExecutorService downloadExecutorService,
                        final ExecutorService persistenceExecutorService,
                        final Timer timer) {
        mRoot = root;
        mFileHelper = fileHelper;

        mTrackedSourceURLs = trackedSourceURLs;

        mJobFactory = jobFactory;
        mDownloadExecutorService = MoreExecutors.listeningDecorator(downloadExecutorService);
        mPersistenceExecutorService = persistenceExecutorService;
        mUpdateTimer = timer;
    }

    @Override
    public synchronized Future getCatalog(final SourceURL sourceURL) {
        mTrackedSourceURLs.add(sourceURL);

        if (!mFutures.containsKey(sourceURL)) {
            ListenableFuture result;

            final File catalogFile = getCatalogFile(sourceURL);

            try {
                result = getPersistedCatalog(sourceURL, catalogFile);
            } catch (final FileNotFoundException e) {
                result = downloadCatalog(sourceURL, catalogFile);
            }

            cacheFuture(sourceURL, result);
        }

        return mFutures.get(sourceURL);
    }

    /**
     * Must be called before scheduling updates.
     */
    @Override
    public synchronized boolean clearCachedCatalogs() {
        if (mUpdateScheduled) {
            throw new ZincRuntimeException("Updates were already scheduled");
        }

        return mFileHelper.emptyDirectory(getCatalogsFolder());
    }

    @Override
    public void scheduleUpdate() {
        if (!mUpdateScheduled) {
            mUpdateScheduled = true;

            mUpdateTimer.schedule(new TimerTask() {
                @Override public void run() {
                    updateCatalogsForTrackedSourceURLs();
                }
            }, INITIAL_UPDATE_DELAY, UPDATE_FREQUENCY);
        }
    }

    private synchronized SettableFuture getPersistedCatalog(final SourceURL sourceURL, final File catalogFile) throws FileNotFoundException {
        final ZincCatalog zincCatalog = readCatalogFile(catalogFile);

        final SettableFuture future = SettableFuture.create();
        future.set(zincCatalog);

        logMessage(sourceURL.getCatalogID(), "Returning persisted catalog");

        return future;
    }

    private synchronized ListenableFuture downloadCatalog(final SourceURL sourceURL, final File catalogFile) {
        final ListenableFuture originalFuture = mFutures.get(sourceURL);
        final ListenableFuture result = mDownloadExecutorService.submit(mJobFactory.downloadCatalog(sourceURL));

        Futures.addCallback(result, new FutureCallback() {
            @Override public void onSuccess(final ZincCatalog result) {
                persistCatalog(result, catalogFile);
            }

            @Override public void onFailure(final Throwable downloadFailed) {
                logMessage(sourceURL.getCatalogID(), "Failed to download");

                if (originalFuture != null) {
                    cacheFuture(sourceURL, originalFuture);
                } else {
                    removeFuture(sourceURL);
                }
            }
        }, mPersistenceExecutorService);

        return result;
    }

    private synchronized void persistCatalog(final ZincCatalog result, final File catalogFile) {
        try {
            logMessage(result.getIdentifier(), "Persisting catalog to disk: " + result.getIdentifier());

            mFileHelper.writeObject(catalogFile, result, ZincCatalog.class);
        } catch (final IOException e) {
            logMessage(result.getIdentifier(), "Error persisting catalog to disk: " + e);
        }
    }

    private synchronized void cacheFuture(final SourceURL sourceURL, final ListenableFuture future) {
        mFutures.put(sourceURL, future);
    }

    private synchronized void removeFuture(final SourceURL sourceURL) {
        mFutures.remove(sourceURL);
    }

    private synchronized void updateCatalogsForTrackedSourceURLs() {
        logMessage("All", "Updating catalogs for tracked source URLs");

        for (final SourceURL sourceURL : mTrackedSourceURLs) {
            final ListenableFuture future = downloadCatalog(sourceURL, getCatalogFile(sourceURL));

            if (!future.isDone()) { // otherwise downloadCatalog has already cached the result
                cacheFuture(sourceURL, future);
            }
        }
    }

    private File getCatalogsFolder() {
        return new File(mRoot, PathHelper.getCatalogsFolder());
    }

    private File getCatalogFile(final SourceURL sourceURL) {
        return new File(mRoot, PathHelper.getLocalCatalogFilePath(sourceURL.getCatalogID()));
    }

    private ZincCatalog readCatalogFile(final File catalogFile) throws FileNotFoundException {
        return mFileHelper.readJSON(catalogFile, ZincCatalog.class);
    }

    private void logMessage(final String catalogID, final String message) {
        ZincLogging.log(getClass().getSimpleName() + " (" + catalogID + ")", message);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy