001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import java.util.List;
005import java.util.concurrent.Executors;
006import java.util.concurrent.ScheduledExecutorService;
007import java.util.concurrent.ScheduledFuture;
008import java.util.concurrent.TimeUnit;
009import java.util.stream.Collectors;
010
011import org.openstreetmap.josm.data.osm.Changeset;
012import org.openstreetmap.josm.data.osm.ChangesetCache;
013import org.openstreetmap.josm.data.preferences.IntegerProperty;
014import org.openstreetmap.josm.tools.Logging;
015import org.openstreetmap.josm.tools.Utils;
016
017/**
018 * Checks periodically if open changesets have been closed on server side.
019 * @since 14326
020 */
021public final class ChangesetUpdater {
022
023    private ChangesetUpdater() {
024        // Hide default constructor for utils classes
025    }
026
027    /** Property defining the update interval in minutes */
028    public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("changeset.updater.interval", 60);
029
030    private static final ScheduledExecutorService EXECUTOR =
031            Executors.newSingleThreadScheduledExecutor(Utils.newThreadFactory("changeset-updater-%d", Thread.NORM_PRIORITY));
032
033    private static final Runnable WORKER = new Worker();
034
035    private static volatile ScheduledFuture<?> task;
036
037    private static class Worker implements Runnable {
038
039        private long lastTimeInMillis;
040
041        @Override
042        public void run() {
043            long currentTime = System.currentTimeMillis();
044            // See #14671 - Make sure we don't run the API call many times after system wakeup
045            if (currentTime >= lastTimeInMillis + TimeUnit.MINUTES.toMillis(PROP_INTERVAL.get())) {
046                lastTimeInMillis = currentTime;
047                check();
048            }
049        }
050    }
051
052    /**
053     * Checks for open changesets that have been closed on server side, and update Changeset cache if needed.
054     */
055    public static void check() {
056        long now = System.currentTimeMillis();
057        List<Long> changesetIds = ChangesetCache.getInstance().getOpenChangesets().stream()
058            .filter(x -> x.getCreatedAt() != null
059                && now - x.getCreatedAt().toEpochMilli() > TimeUnit.HOURS.toMillis(1))
060            .map(Changeset::getId)
061            .map(Integer::longValue)
062            .collect(Collectors.toList());
063        if (!changesetIds.isEmpty()) {
064            try {
065                ChangesetCache.getInstance().update(new OsmServerChangesetReader().queryChangesets(
066                        new ChangesetQuery().forChangesetIds(changesetIds), null));
067            } catch (OsmTransferException e) {
068                Logging.warn(e);
069            }
070        }
071    }
072
073    /**
074     * Starts the changeset updater task if not already started
075     */
076    public static void start() {
077        int interval = PROP_INTERVAL.get();
078        if (!isRunning() && interval > 0) {
079            task = EXECUTOR.scheduleAtFixedRate(WORKER, 0, interval, TimeUnit.MINUTES);
080            Logging.info("Changeset updater active (checks every "+interval+" minute"+(interval > 1 ? "s" : "") +
081                    " if open changesets have been closed)");
082        }
083    }
084
085    /**
086     * Stops the changeset updater task if started
087     */
088    public static void stop() {
089        if (isRunning()) {
090            task.cancel(false);
091            Logging.info("Changeset updater inactive");
092            task = null;
093        }
094    }
095
096    /**
097     * Determines if the changeset updater is currently running
098     * @return {@code true} if the updater is running, {@code false} otherwise
099     */
100    public static boolean isRunning() {
101        return task != null;
102    }
103}