Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to download additional tasks from a specific challenge #14

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading