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}