Skip to content

Commit

Permalink
Allow users to download additional tasks from a specific challenge
Browse files Browse the repository at this point in the history
Signed-off-by: Taylor Smock <tsmock@meta.com>
  • Loading branch information
tsmock committed Jul 9, 2024
1 parent 4fbae41 commit 197357c
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.maproulette.actions;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.List;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.plugins.maproulette.actions.downloadtasks.MapRouletteDownloadTask;
import org.openstreetmap.josm.plugins.maproulette.api.model.TaskClusteredPoint;
import org.openstreetmap.josm.tools.Shortcut;

/**
* Download additional nearby tasks for that challenge
*/
public class DownloadTasks extends JosmAction {
/**
* Create a new action for downloading tasks
*/
public DownloadTasks() {
super(tr("Download additional tasks"), "download", tr("Download additional tasks from challenge"),
Shortcut.registerShortcut("maproulette:download_additional_tasks_from_challenge",
tr("MapRoulette: Download additional tasks from challenge"), KeyEvent.CHAR_UNDEFINED,
Shortcut.NONE),
false);
}

@Override
public void actionPerformed(ActionEvent e) {
final var component = ((JPopupMenu) ((JMenuItem) e.getSource()).getParent()).getInvoker();
final List<?> objects = ActionUtils.getSelectedItems(component);
final var locations = new ArrayDeque<TaskClusteredPoint>(objects.size());
for (var obj : objects) {
if (obj instanceof TaskClusteredPoint t) {
locations.add(t);
}
}
while (!locations.isEmpty()) {
final var t = locations.pop();
new MapRouletteDownloadTask(t.parentId(), t.id()).loadUrl(new DownloadParams(), "",
NullProgressMonitor.INSTANCE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
Expand All @@ -18,23 +19,44 @@
import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.gui.progress.ProgressTaskId;
import org.openstreetmap.josm.io.OsmTransferException;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.plugins.maproulette.api.ChallengeAPI;
import org.openstreetmap.josm.plugins.maproulette.api.TaskAPI;
import org.openstreetmap.josm.plugins.maproulette.api.model.Task;
import org.openstreetmap.josm.plugins.maproulette.api.model.TaskClusteredPoint;
import org.openstreetmap.josm.plugins.maproulette.api_caching.ChallengeCache;
import org.openstreetmap.josm.plugins.maproulette.gui.layer.MapRouletteClusteredPointLayer;
import org.xml.sax.SAXException;

/**
* A download task for downloading specific tasks
*/
public class MapRouletteDownloadTask extends AbstractDownloadTask<Collection<Task>> {
private static final Pattern PATTERN_CHALLENGE_TASK = Pattern.compile(".*/challenge/(\\d+)/task/(\\d+)");
private DownloadTask downloadTask;
private static final Pattern PATTERN_CHALLENGE = Pattern.compile(".*/challenges/(\\d+)");
private final long challenge;
private final long task;
private PleaseWaitRunnable downloadTask;

/**
* The default constructor
*/
public MapRouletteDownloadTask() {
this(-1, -1);
}

/**
* Download tasks from a specific challenge and optionally around the specified task
* @param challenge The challenge to download from
* @param task The task to download around
*/
public MapRouletteDownloadTask(long challenge, long task) {
this.challenge = challenge;
this.task = task;
}

@Override
public Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) {
Expand All @@ -49,64 +71,65 @@ public Future<?> loadUrl(DownloadParams settings, String url, ProgressMonitor pr
this.downloadTask = new DownloadTask(progressMonitor, Long.parseLong(taskId));
return MainApplication.worker.submit(this.downloadTask);
}
final var challengeMatcher = PATTERN_CHALLENGE.matcher(url);
if (challengeMatcher.matches()) {
final var challengeId = Long.parseLong(challengeMatcher.group(1));
this.downloadTask = new DownloadChallenge(progressMonitor, challengeId, -1);
return MainApplication.worker.submit(this.downloadTask);
}
if (challenge > 0) {
this.downloadTask = new DownloadChallenge(progressMonitor, challenge, task);
return MainApplication.worker.submit(this.downloadTask);
}
return null;
}

@Override
public void cancel() {
this.downloadTask.cancel();
if (this.downloadTask instanceof DownloadChallenge c) {
c.cancel();
} else if (this.downloadTask instanceof DownloadTask t) {
t.cancel();
} else {
throw new UnsupportedOperationException("Cannot cancel " + this.downloadTask);
}
}

@Override
public String getConfirmationMessage(URL url) {
return tr("Download task {0}", url.toExternalForm());
if (PATTERN_CHALLENGE_TASK.matcher(url.toExternalForm()).matches()) {
return tr("Download task {0}", url.toExternalForm());
}
if (PATTERN_CHALLENGE.matcher(url.toExternalForm()).matches()) {
return tr("Download challenge {0}", url.toExternalForm());
}
throw new IllegalArgumentException("Unsupported url: " + url);
}

@Override
public String[] getPatterns() {
return new String[] { "https?://.*" + PATTERN_CHALLENGE_TASK.pattern() };
return new String[] { "https?://.*" + PATTERN_CHALLENGE_TASK.pattern(),
"https?://.*" + PATTERN_CHALLENGE.pattern() };
}

@Override
public String getTitle() {
return tr("Download MapRoulette Task");
}

private static class DownloadTask extends PleaseWaitRunnable {
private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download task");
private final long[] taskIds;
private Collection<Task> tasks;
private boolean isCancelled;
private abstract static class DownloadParent extends PleaseWaitRunnable {
protected Collection<Task> tasks;
protected boolean isCancelled;

/**
* Create a new download task
*
* @param progressMonitor The progress monitor to use
* @param ids The ids to download
*/
DownloadTask(ProgressMonitor progressMonitor, long... ids) {
super(tr("Downloading MapRoulette Task {0}", ids[0]), progressMonitor, false);
this.taskIds = ids;
protected DownloadParent(String title, ProgressMonitor progressMonitor, boolean ignoreException) {
super(title, progressMonitor, ignoreException);
}

@Override
protected void cancel() {
this.isCancelled = true;
}

@Override
protected void realRun() throws SAXException, IOException, OsmTransferException {
this.tasks = new ArrayList<>();
for (var taskId : this.taskIds) {
if (this.isCancelled) {
return;
}
final var task = TaskAPI.get(taskId);
this.tasks.add(task);
ChallengeCache.challenge(task.parentId());
}
}

@Override
protected void finish() {
if (tasks != null && !tasks.isEmpty()) {
Expand All @@ -130,6 +153,73 @@ protected void finish() {
}
}
}
}

private static class DownloadChallenge extends DownloadParent {
private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download challenge");
private final long challengeId;
private final long task;

/**
* Create a new download task
*
* @param progressMonitor The progress monitor to use
* @param id The id to download
* @param task The optional task to get tasks around
*/
DownloadChallenge(ProgressMonitor progressMonitor, long id, long task) {
super(tr("Downloading MapRoulette Challenge {0}", id), progressMonitor, false);
this.challengeId = id;
this.task = task;
}

@Override
protected void realRun() throws IOException {
this.tasks = new ArrayList<>();
final var challenge = ChallengeCache.challenge(this.challengeId);
if (!this.isCancelled) {
if (challenge.tasksRemaining() != null && challenge.tasksRemaining() > 0) {
this.tasks
.addAll(Arrays.asList(ChallengeAPI.randomTask(challenge.id(), null, null, 10, this.task)));
} else {
GuiHelper.runInEDT(() -> new Notification(tr("Challenge may be done")).show());
}
}
}

@Override
public ProgressTaskId canRunInBackground() {
return PROGRESS_TASK_ID;
}
}

private static class DownloadTask extends DownloadParent {
private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download task");
private final long[] taskIds;

/**
* Create a new download task
*
* @param progressMonitor The progress monitor to use
* @param ids The ids to download
*/
DownloadTask(ProgressMonitor progressMonitor, long... ids) {
super(tr("Downloading MapRoulette Task {0}", ids[0]), progressMonitor, false);
this.taskIds = ids;
}

@Override
protected void realRun() throws IOException {
this.tasks = new ArrayList<>();
for (var taskId : this.taskIds) {
if (this.isCancelled) {
return;
}
final var task = TaskAPI.get(taskId);
this.tasks.add(task);
ChallengeCache.challenge(task.parentId());
}
}

@Override
public ProgressTaskId canRunInBackground() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.plugins.maproulette.api.MRColors;
import org.openstreetmap.josm.plugins.maproulette.api.enums.TaskStatus;
import org.openstreetmap.josm.plugins.maproulette.api.model.Identifier;
Expand Down Expand Up @@ -157,6 +158,7 @@ public synchronized void refreshTasks(Map<Long, ? extends TaskClusteredPoint> tc
final Map<Long, TaskClusteredPoint> updateCollection = this.pointBucket.stream()
.collect(Collectors.toMap(Identifier::id, c -> c));
this.updatedDataListeners.fireEvent(consumer -> consumer.accept(updateCollection));
GuiHelper.runInEDT(this::invalidate);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.gui.widgets.FilterField;
import org.openstreetmap.josm.plugins.maproulette.actions.DownloadTasks;
import org.openstreetmap.josm.plugins.maproulette.actions.GoToTaskLocation;
import org.openstreetmap.josm.plugins.maproulette.actions.IgnoreAction;
import org.openstreetmap.josm.plugins.maproulette.actions.downloadtasks.MapRouletteDownloadTaskBox;
Expand Down Expand Up @@ -125,6 +126,7 @@ public boolean include(Entry<? extends TaskTableModel, ? extends Integer> entry)
layers.forEach(MapRouletteClusteredPointLayer::invalidate);
});
menu.add(new GoToTaskLocation());
menu.add(new DownloadTasks());
menu.add(new IgnoreAction(IgnoreAction.IgnoreType.IGNORE_TASK));
menu.add(new IgnoreAction(IgnoreAction.IgnoreType.IGNORE_CHALLENGE));
table.setComponentPopupMenu(menu);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public static void fireIgnoreAction(TaskListPanel panel, IgnoreAction.IgnoreType
.getComponent(0 /* A panel */)).getComponent(2 /* The table */));
final var menu = table.getComponentPopupMenu();
final var menuItem = (JMenuItem) switch (ignoreType) {
case IGNORE_TASK -> menu.getComponent(1);
case IGNORE_CHALLENGE -> menu.getComponent(2);
case IGNORE_TASK -> menu.getComponent(2);
case IGNORE_CHALLENGE -> menu.getComponent(3);
};
menu.setInvoker(table);
final var action = menuItem.getAction();
Expand Down

0 comments on commit 197357c

Please sign in to comment.