Package ru.aristar.jnuget.sources

Source Code of ru.aristar.jnuget.sources.IndexedPackageSource

package ru.aristar.jnuget.sources;

import it.sauronsoftware.cron4j.InvalidPatternException;
import it.sauronsoftware.cron4j.Scheduler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import static java.text.MessageFormat.format;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.aristar.jnuget.Version;
import ru.aristar.jnuget.files.Nupkg;
import ru.aristar.jnuget.sources.push.ModifyStrategy;

/**
* Индексируемое хранилище пакетов
*
* @author sviridov
*/
public class IndexedPackageSource implements PackageSource<Nupkg>, AutoCloseable {

    /**
     * Индекс пакетов
     */
    private volatile Index index = new Index();
    /**
     * Индексируемый источник пакетов
     */
    private PackageSource<? extends Nupkg> packageSource;
    /**
     * Имя файла индекса
     */
    private File indexStoreFile = null;
    /**
     * Логгер
     */
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * Таймер обновления индекса
     */
    private Timer timer;
    /**
     * Интервал обновления индекса храниилща
     */
    private Integer refreshInterval;
    /**
     * Семафор, использующийся при помещении пакета в хранилище
     */
    private final Semaphore pushSemaphore = new Semaphore(1);
    //TODO RefreshIndex semaphore
    /**
     * Индекс находистя в процессе обновления
     */
    private volatile boolean refreshIndexInProgress = false;
    /**
     * Очередь пакетов, ожидающих помещение в хранилище
     */
    private BlockingQueue<Nupkg> newPackageQueue = new LinkedBlockingQueue<>();
    /**
     * Планировщик обновления индекса.
     */
    private Scheduler scheduler;

    @Override
    public void refreshPackage(Nupkg nupkg) {
        packageSource.refreshPackage(nupkg);
    }

    @Override
    public String getName() {
        return packageSource.getName();
    }

    @Override
    public void setName(String storageName) {
        packageSource.setName(storageName);
    }

    @Override
    public void close() throws Exception {
        if (scheduler != null && scheduler.isStarted()) {
            scheduler.stop();
        }
    }

    /**
     * Поток, обновляющий индекс
     */
    private class RefreshIndexThread extends TimerTask {

        /**
         * Основной метод потока, обновляющий индекс
         */
        @Override
        public void run() {
            try {
                if (refreshIndexInProgress) {
                    logger.info("Предыдущая задача обновления индекса не успела завершится");
                    return;
                }
                refreshIndex();
            } catch (Exception e) {
                logger.error("Ошибка оновления индекса для хранилища " + packageSource, e);
            }
        }
    }

    /**
     * Перечитывает индекс хранилища
     *
     * @throws InterruptedException обновление индекса было прервано
     * @throws IOException ошибка чтения/записи пакета
     */
    private void refreshIndex() throws InterruptedException, IOException {
        synchronized (this) {
            logger.info("Инициировано обновление индекса хранилища {}", new Object[]{packageSource});
            refreshIndexInProgress = true;
            try {
                Collection<? extends Nupkg> packages = packageSource.getPackages();
                Index newIndex = new Index();
                for (Nupkg nupkg : packages) {
                    try {
                        if (nupkg == null) {
                            continue;
                        }
                        nupkg.load();
                        newIndex.put(nupkg);
                    } catch (IOException e) {
                        logger.warn("Ошибка инициализации пакета", e);
                    }
                }
                logger.info("Добавление в индекс, ожидающих пакетов");
                try {
                    pushSemaphore.acquire();
                    logger.info(format("Ожидает добавления {0} пакетов", newPackageQueue.size()));
                    while (!newPackageQueue.isEmpty()) {
                        final Nupkg nupkg = newPackageQueue.poll();
                        packageSource.pushPackage(nupkg);
                        if (nupkg != null) {
                            newIndex.put(nupkg);
                        }
                    }
                } finally {
                    pushSemaphore.release();
                }
                this.index = newIndex;
                if (indexStoreFile != null) {
                    try (FileOutputStream fileOutputStream = new FileOutputStream(indexStoreFile)) {
                        index.saveTo(fileOutputStream);
                    } catch (Exception e) {
                        logger.warn("Не удалось сохранить локальную копию индекса", e);
                    }
                }
                logger.info("Обновление индекса хранилища {} завершено. Обнаружено {} пакетов",
                        new Object[]{packageSource, index.size()});
                this.notifyAll();
            } finally {
                refreshIndexInProgress = false;
            }
        }
    }

    /**
     * Возвращает индекс хранилища
     *
     * @return индекс хранилища
     */
    public Index getIndex() {
        if (index == null) {
            try {
                synchronized (this) {
                    while (index == null) {
                        logger.warn("Индекс не создан, ожидание создания индекса");
                        this.wait();
                    }
                }
            } catch (InterruptedException e) {
                logger.error("Ожидание загрузки индекса прервано. Остановка потока");
            }
        }
        return index;
    }

    @Override
    public Collection<Nupkg> getPackages() {
        ArrayList<Nupkg> result = new ArrayList<>();
        Iterator<Nupkg> iterator = getIndex().getAllPackages();
        while (iterator.hasNext()) {
            result.add(iterator.next());
        }
        return result;
    }

    @Override
    public Collection<Nupkg> getLastVersionPackages() {
        ArrayList<Nupkg> result = new ArrayList<>();
        Iterator<Nupkg> iterator = getIndex().getLastVersions();
        while (iterator.hasNext()) {
            Nupkg nupkg = iterator.next();
            if (nupkg != null) {
                result.add(nupkg);
            } else {
                logger.warn("Индекс для хранилища {} содержит null пакеты", new Object[]{packageSource});
            }
        }
        return result;
    }

    @Override
    public Collection<Nupkg> getPackages(String id) {
        return getIndex().getPackageById(id);
    }

    @Override
    public Nupkg getLastVersionPackage(String id) {
        return getIndex().getLastVersion(id);
    }

    @Override
    public Nupkg getPackage(String id, Version version) {
        return getIndex().getPackage(id, version);
    }

    @Override
    public boolean pushPackage(Nupkg file) throws IOException {
        try {
            pushSemaphore.acquire();
            if (refreshIndexInProgress) {
                if (!packageSource.getPushStrategy().canPush()) {
                    return false;
                }
                newPackageQueue.add(file);
                return true;
            } else {
                boolean result = packageSource.pushPackage(file);
                if (result) {
                    Nupkg localFile = packageSource.getPackage(file.getId(), file.getVersion());
                    getIndex().put(localFile);
                }
                return result;
            }
        } catch (InterruptedException e) {
            throw new IOException(e);
        } finally {
            pushSemaphore.release();
        }
    }

    @Override
    public ModifyStrategy getPushStrategy() {
        return packageSource.getPushStrategy();
    }

    @Override
    public void setPushStrategy(ModifyStrategy strategy) {
        packageSource.setPushStrategy(strategy);
    }

    @Override
    public void removePackage(Nupkg nupkg) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * @return источник пакетов, подлежащий индексированию
     */
    public PackageSource<? extends Nupkg> getUnderlyingSource() {
        return packageSource;
    }

    /**
     * @param packageSource источник пакетов, подлежащий индексированию
     */
    public void setUnderlyingSource(PackageSource<? extends Nupkg> packageSource) {
        setUnderlyingSource(packageSource, false);
    }

    /**
     * @param packageSource источник пакетов, подлежащий индексированию
     * @param forseRescan инициировать пересканирование хранилища
     * @return поток обновления индекса
     */
    public Thread setUnderlyingSource(PackageSource<? extends Nupkg> packageSource, boolean forseRescan) {
        this.packageSource = packageSource;
        if (forseRescan) {
            Thread thread = new Thread(new IndexedPackageSource.RefreshIndexThread());
            thread.start();
            return thread;
        } else {
            return null;
        }
    }

    /**
     * Устанавливает интервал обновления информации о хранилище
     *
     * @param interval интервал обновления информации в минутах
     */
    public void setRefreshInterval(Integer interval) {
        this.refreshInterval = interval;
        if (timer != null) {
            timer.cancel();
            timer.purge();
        }
        if (refreshInterval == null) {
            return;
        }
        final long intervalMs = refreshInterval * 60000;
        RefreshIndexThread indexThread = new IndexedPackageSource.RefreshIndexThread();
        if (timer == null) {
            timer = new Timer();
        }
        timer.schedule(indexThread, intervalMs, intervalMs);
    }

    /**
     * Запланировать обновление информации о хранилище
     *
     * @param cronString строка cron
     */
    public void setCronSheduller(String cronString) {
        try {
            if (scheduler != null) {
                scheduler.stop();
            }
            scheduler = new Scheduler();
            scheduler.schedule(cronString, new RefreshIndexThread());
            if (!scheduler.isStarted()) {
                scheduler.start();
            }
        } catch (IllegalStateException | InvalidPatternException e) {
            logger.error(format("Не удалось запланировать обновление индекса с параметрами {0}", cronString), e);
        }
    }

    /**
     * @return интервал обновления индекса хранилища
     */
    public Integer getRefreshInterval() {
        return refreshInterval;
    }

    /**
     * @param indexStoreFile файл для хранения индекса
     */
    public void setIndexStoreFile(File indexStoreFile) {
        this.indexStoreFile = indexStoreFile;

        if (this.indexStoreFile != null && this.indexStoreFile.exists()) {
            logger.info("Для хранилища {} обнаружен локально сохраненный файл "
                    + "индекса", new Object[]{packageSource});
            try (FileInputStream fileInputStream = new FileInputStream(this.indexStoreFile)) {
                this.index = Index.loadFrom(fileInputStream);
                logger.info("Индекс загружен в память из локального файла \"{}\"", new Object[]{this.indexStoreFile});
                Iterator<Nupkg> iterator = this.index.getAllPackages();
                while (iterator.hasNext()) {
                    Nupkg nupkg = iterator.next();
                    this.packageSource.refreshPackage(nupkg);
                }
                logger.info("Индекс просканирован. Обнаружено {} пакетов",
                        new Object[]{index.size()});
            } catch (Exception e) {
                indexStoreFile.delete();
                logger.warn("Не удалось прочитать локально сохраненный индекс", e);
            }
        } else {
            logger.info("Локально сохраненный файл индекса для хранилища {} не "
                    + "обнаружен", new Object[]{packageSource});
        }
    }

    /**
     * @return файл для хранения индекса
     */
    public File getIndexStoreFile() {
        return indexStoreFile;
    }

    /**
     * Создает файл для хранения индекса
     *
     * @param path путь к каталогу, в котором требуется создать файл
     * @param storageName имя хранилища
     * @return файл для хранения индекса
     */
    public static File getIndexSaveFile(File path, String storageName) {
        return new File(path, storageName + ".idx");
    }
}
TOP

Related Classes of ru.aristar.jnuget.sources.IndexedPackageSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.