Resolve map author names locally
This commit is contained in:
parent
8a79575b18
commit
c06e44fdb3
|
@ -5,6 +5,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -50,6 +51,12 @@ public interface MapDoc extends Model {
|
|||
Collection<UUID> author_uuids();
|
||||
Collection<UUID> contributor_uuids();
|
||||
|
||||
@Serialize(false)
|
||||
default Stream<UUID> authorAndContributorUuids() {
|
||||
return Stream.concat(author_uuids().stream(),
|
||||
contributor_uuids().stream());
|
||||
}
|
||||
|
||||
@Serialize
|
||||
interface Team extends Model {
|
||||
@Nonnull String name();
|
||||
|
|
|
@ -13,5 +13,8 @@ public interface MapService extends ModelService<MapDoc, MapDoc> {
|
|||
|
||||
ListenableFuture<MapRatingsResponse> getRatings(MapRatingsRequest request);
|
||||
|
||||
ListenableFuture<MapUpdateMultiResponse> updateMapsAndLookupAuthors(Collection<? extends MapDoc> maps);
|
||||
/**
|
||||
* Send map updates to the backend, and retrieve data about map contributors.
|
||||
*/
|
||||
UpdateMapsResponse updateMaps(Collection<? extends MapDoc> maps);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.google.common.util.concurrent.Futures;
|
|||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import tc.oc.api.docs.MapRating;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.api.message.types.UpdateMultiResponse;
|
||||
import tc.oc.api.model.NullModelService;
|
||||
|
||||
public class NullMapService extends NullModelService<MapDoc, MapDoc> implements MapService {
|
||||
|
@ -22,7 +23,10 @@ public class NullMapService extends NullModelService<MapDoc, MapDoc> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<MapUpdateMultiResponse> updateMapsAndLookupAuthors(Collection<? extends MapDoc> maps) {
|
||||
return Futures.immediateFuture(new MapUpdateMultiResponse(Collections.emptyMap()));
|
||||
public UpdateMapsResponse updateMaps(Collection<? extends MapDoc> maps) {
|
||||
return new UpdateMapsResponse(
|
||||
Futures.immediateFuture(UpdateMultiResponse.EMPTY),
|
||||
Collections.emptyMap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package tc.oc.api.maps;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import tc.oc.api.docs.virtual.UserDoc;
|
||||
import tc.oc.api.message.types.UpdateMultiResponse;
|
||||
|
||||
/**
|
||||
* Result of {@link MapService#updateMaps(Collection)}
|
||||
*/
|
||||
public class UpdateMapsResponse {
|
||||
|
||||
private final ListenableFuture<UpdateMultiResponse> maps;
|
||||
private final Map<UUID, ListenableFuture<UserDoc.Identity>> authors;
|
||||
|
||||
public UpdateMapsResponse(ListenableFuture<UpdateMultiResponse> maps, Map<UUID, ListenableFuture<UserDoc.Identity>> authors) {
|
||||
this.maps = maps;
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of updating all the maps
|
||||
*/
|
||||
public ListenableFuture<UpdateMultiResponse> maps() {
|
||||
return maps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of each individual map contributor lookup,
|
||||
* which may not all arrive at the same time.
|
||||
*/
|
||||
public Map<UUID, ListenableFuture<UserDoc.Identity>> authors() {
|
||||
return authors;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import javax.annotation.Nullable;
|
|||
import tc.oc.api.annotations.Serialize;
|
||||
import tc.oc.api.docs.PlayerId;
|
||||
import tc.oc.api.docs.virtual.Document;
|
||||
import tc.oc.minecraft.api.user.UserUtils;
|
||||
|
||||
public class UserSearchRequest implements Document {
|
||||
@Serialize public final String username;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package tc.oc.api.users;
|
||||
|
||||
import tc.oc.commons.core.formatting.StringUtils;
|
||||
|
||||
public interface UserUtils {
|
||||
|
||||
static String sanitizeUsername(String username) {
|
||||
return StringUtils.truncate(username.replaceAll("[^A-Za-z0-9_]", ""), 16);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package tc.oc.api.minecraft.maps;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
|
@ -13,9 +12,12 @@ import tc.oc.api.docs.virtual.MapDoc;
|
|||
import tc.oc.api.maps.MapRatingsRequest;
|
||||
import tc.oc.api.maps.MapRatingsResponse;
|
||||
import tc.oc.api.maps.MapService;
|
||||
import tc.oc.api.maps.MapUpdateMultiResponse;
|
||||
import tc.oc.api.maps.UpdateMapsResponse;
|
||||
import tc.oc.api.message.types.UpdateMultiResponse;
|
||||
import tc.oc.api.minecraft.users.UserStore;
|
||||
import tc.oc.api.model.NullModelService;
|
||||
import tc.oc.api.users.UserService;
|
||||
import tc.oc.api.util.UUIDs;
|
||||
import tc.oc.commons.core.stream.Collectors;
|
||||
import tc.oc.minecraft.api.entity.Player;
|
||||
|
||||
|
@ -23,6 +25,7 @@ import tc.oc.minecraft.api.entity.Player;
|
|||
public class LocalMapService extends NullModelService<MapDoc, MapDoc> implements MapService {
|
||||
|
||||
@Inject private UserStore<Player> userStore;
|
||||
@Inject private UserService userService;
|
||||
|
||||
@Override
|
||||
public ListenableFuture<?> rate(MapRating rating) {
|
||||
|
@ -35,14 +38,13 @@ public class LocalMapService extends NullModelService<MapDoc, MapDoc> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<MapUpdateMultiResponse> updateMapsAndLookupAuthors(Collection<? extends MapDoc> maps) {
|
||||
return Futures.immediateFuture(new MapUpdateMultiResponse(
|
||||
public UpdateMapsResponse updateMaps(Collection<? extends MapDoc> maps) {
|
||||
return new UpdateMapsResponse(
|
||||
Futures.immediateFuture(UpdateMultiResponse.EMPTY),
|
||||
maps.stream()
|
||||
.flatMap(map -> Stream.concat(map.author_uuids().stream(),
|
||||
map.contributor_uuids().stream()))
|
||||
.collect(Collectors.mappingTo(uuid -> userStore.byUuid(uuid)
|
||||
.flatMap(userStore::user)
|
||||
.orElse(null)))
|
||||
));
|
||||
.flatMap(MapDoc::authorAndContributorUuids)
|
||||
.distinct()
|
||||
.collect(Collectors.mappingTo(uuid -> (ListenableFuture) userService.find(() -> UUIDs.normalize(uuid))))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import tc.oc.api.docs.virtual.MapDoc;
|
|||
import tc.oc.api.docs.virtual.MatchDoc;
|
||||
import tc.oc.api.docs.virtual.ServerDoc;
|
||||
import tc.oc.api.minecraft.config.MinecraftApiConfiguration;
|
||||
import tc.oc.minecraft.api.entity.OfflinePlayer;
|
||||
import tc.oc.minecraft.api.user.User;
|
||||
import tc.oc.minecraft.api.server.LocalServer;
|
||||
|
||||
@Singleton
|
||||
|
@ -162,8 +162,8 @@ public class LocalServerDocument extends StartupServerDocument implements Server
|
|||
@Override
|
||||
public Map<UUID, String> operators() {
|
||||
final ImmutableMap.Builder<UUID, String> ops = ImmutableMap.builder();
|
||||
for(OfflinePlayer op : minecraftServer.getOperators()) {
|
||||
ops.put(op.getUniqueId(), op.getLastKnownName().orElse("Player"));
|
||||
for(User op : minecraftServer.getOperators()) {
|
||||
ops.put(op.getUniqueId(), op.name().orElse("Player"));
|
||||
}
|
||||
return ops.build();
|
||||
}
|
||||
|
|
|
@ -15,18 +15,24 @@ import tc.oc.api.docs.SimplePlayerId;
|
|||
import tc.oc.api.docs.User;
|
||||
import tc.oc.api.docs.virtual.UserDoc;
|
||||
import tc.oc.api.minecraft.servers.DefaultPermissions;
|
||||
import tc.oc.minecraft.api.entity.OfflinePlayer;
|
||||
|
||||
public class LocalUserDocument extends SimplePlayerId implements User {
|
||||
|
||||
private final OfflinePlayer player;
|
||||
private final UUID uuid;
|
||||
private final String ip;
|
||||
|
||||
public LocalUserDocument(OfflinePlayer player) {
|
||||
super(player.getUniqueId().toString(),
|
||||
player.getUniqueId().toString(),
|
||||
player.getLastKnownName().orElse(""));
|
||||
LocalUserDocument(UUID uuid, String name, String ip) {
|
||||
super(uuid.toString(), uuid.toString(), name);
|
||||
this.uuid = uuid;
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
this.player = player;
|
||||
LocalUserDocument(tc.oc.minecraft.api.user.User user) {
|
||||
this(user.getUniqueId(),
|
||||
user.getName(),
|
||||
user.onlinePlayer()
|
||||
.map(p -> p.getAddress().getHostString())
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,7 +47,7 @@ public class LocalUserDocument extends SimplePlayerId implements User {
|
|||
|
||||
@Override
|
||||
public UUID uuid() {
|
||||
return player.getUniqueId();
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,9 +82,7 @@ public class LocalUserDocument extends SimplePlayerId implements User {
|
|||
|
||||
@Override
|
||||
public String mc_last_sign_in_ip() {
|
||||
return player.onlinePlayer()
|
||||
.map(p -> p.getAddress().getHostString())
|
||||
.orElse("");
|
||||
return ip;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,39 +30,43 @@ import tc.oc.api.users.UserSearchResponse;
|
|||
import tc.oc.api.users.UserService;
|
||||
import tc.oc.api.users.UserUpdateResponse;
|
||||
import tc.oc.commons.core.concurrent.FutureUtils;
|
||||
import tc.oc.minecraft.api.entity.OfflinePlayer;
|
||||
import tc.oc.minecraft.api.server.LocalServer;
|
||||
import tc.oc.minecraft.api.user.UserFinder;
|
||||
|
||||
@Singleton
|
||||
public class LocalUserService extends NullModelService<User, UserDoc.Partial> implements UserService {
|
||||
class LocalUserService extends NullModelService<User, UserDoc.Partial> implements UserService {
|
||||
|
||||
@Inject private LocalServer minecraftServer;
|
||||
@Inject private LocalSessionFactory sessionFactory;
|
||||
@Inject private UserFinder userFinder;
|
||||
|
||||
@Override
|
||||
public ListenableFuture<User> find(UserId userId) {
|
||||
return Futures.immediateFuture(new LocalUserDocument(minecraftServer.getOfflinePlayer(UUID.fromString(userId.player_id()))));
|
||||
return FutureUtils.mapSync(
|
||||
userFinder.findUserAsync(UUID.fromString(userId.player_id())),
|
||||
user -> {
|
||||
if(user.hasValidId() && user.name().isPresent()) {
|
||||
return new LocalUserDocument(user);
|
||||
}
|
||||
throw new NotFound("No user with UUID " + userId.player_id());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<UserSearchResponse> search(UserSearchRequest request) {
|
||||
for(OfflinePlayer player : minecraftServer.getSavedPlayers()) {
|
||||
if(player.getLastKnownName()
|
||||
.filter(name -> name.equalsIgnoreCase(request.username))
|
||||
.isPresent()) {
|
||||
return Futures.immediateFuture(new UserSearchResponse(new LocalUserDocument(player),
|
||||
player.isOnline(),
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
return FutureUtils.mapSync(
|
||||
userFinder.findUserAsync(request.username),
|
||||
user -> {
|
||||
if(user.hasValidId()) {
|
||||
return new UserSearchResponse(new LocalUserDocument(user), user.isOnline(), false, null, null);
|
||||
}
|
||||
throw new NotFound("No user named '" + request.username + "'");
|
||||
}
|
||||
return Futures.immediateFailedFuture(new NotFound("No user named '" + request.username + "'", null));
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<LoginResponse> login(LoginRequest request) {
|
||||
final User user = new LocalUserDocument(minecraftServer.getOfflinePlayer(request.uuid));
|
||||
final User user = new LocalUserDocument(request.uuid, request.username, request.ip.getHostAddress());
|
||||
final Session session = request.start_session ? sessionFactory.newSession(user, request.ip)
|
||||
: null;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package tc.oc.api.maps;
|
||||
package tc.oc.api.ocn;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
|
@ -6,12 +6,16 @@ import javax.inject.Singleton;
|
|||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import tc.oc.api.docs.MapRating;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.api.docs.virtual.UserDoc;
|
||||
import tc.oc.api.exceptions.NotFound;
|
||||
import tc.oc.api.http.HttpOption;
|
||||
import tc.oc.api.maps.MapRatingsRequest;
|
||||
import tc.oc.api.maps.MapRatingsResponse;
|
||||
import tc.oc.api.maps.MapService;
|
||||
import tc.oc.api.maps.MapUpdateMultiResponse;
|
||||
import tc.oc.api.maps.UpdateMapsResponse;
|
||||
import tc.oc.api.model.HttpModelService;
|
||||
import tc.oc.commons.core.concurrent.FutureUtils;
|
||||
import tc.oc.commons.core.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
class OCNMapService extends HttpModelService<MapDoc, MapDoc> implements MapService {
|
||||
|
@ -24,7 +28,21 @@ class OCNMapService extends HttpModelService<MapDoc, MapDoc> implements MapServi
|
|||
return this.client().post(memberUri(request.map_id, "get_ratings"), request, MapRatingsResponse.class, HttpOption.INFINITE_RETRY);
|
||||
}
|
||||
|
||||
public ListenableFuture<MapUpdateMultiResponse> updateMapsAndLookupAuthors(Collection<? extends MapDoc> maps) {
|
||||
return updateMulti(maps, MapUpdateMultiResponse.class);
|
||||
public UpdateMapsResponse updateMaps(Collection<? extends MapDoc> maps) {
|
||||
final ListenableFuture<MapUpdateMultiResponse> future = updateMulti(maps, MapUpdateMultiResponse.class);
|
||||
return new UpdateMapsResponse(
|
||||
(ListenableFuture) future,
|
||||
maps.stream()
|
||||
.flatMap(MapDoc::authorAndContributorUuids)
|
||||
.distinct()
|
||||
.collect(Collectors.mappingTo(uuid -> FutureUtils.mapSync(
|
||||
future,
|
||||
response -> {
|
||||
final UserDoc.Identity user = response.users_by_uuid.get(uuid);
|
||||
if(user != null) return user;
|
||||
throw new NotFound();
|
||||
}
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.net.URL;
|
|||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -25,6 +26,7 @@ public class MapDocument extends AbstractModel implements MapDoc {
|
|||
private final InfoModule infoModule;
|
||||
private final MapInfo info;
|
||||
private final List<MapDoc.Team> teams;
|
||||
private final List<UUID> authors, contributors;
|
||||
|
||||
@Inject private MapDocument(MapFolder folder, InfoModule infoModule, MapInfo info, List<TeamFactory> teams) {
|
||||
this.folder = folder;
|
||||
|
@ -33,6 +35,16 @@ public class MapDocument extends AbstractModel implements MapDoc {
|
|||
this.teams = teams.stream()
|
||||
.map(TeamFactory::getDocument)
|
||||
.collect(toImmutableList());
|
||||
|
||||
this.authors = info.authors.stream()
|
||||
.map(Contributor::getUuid)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toImmutableList());
|
||||
|
||||
this.contributors = info.contributors.stream()
|
||||
.map(Contributor::getUuid)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,18 +125,12 @@ public class MapDocument extends AbstractModel implements MapDoc {
|
|||
|
||||
@Override
|
||||
public Collection<UUID> author_uuids() {
|
||||
return info.authors.stream()
|
||||
.map(Contributor::getUuid)
|
||||
.filter(uuid -> uuid != null)
|
||||
.collect(toImmutableList());
|
||||
return authors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<UUID> contributor_uuids() {
|
||||
return info.contributors.stream()
|
||||
.map(Contributor::getUuid)
|
||||
.filter(uuid -> uuid != null)
|
||||
.collect(toImmutableList());
|
||||
return contributors;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package tc.oc.pgm.map;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
@ -153,8 +154,8 @@ public class MapInfo implements Comparable<MapInfo> {
|
|||
return Contributor.filterNamed(this.contributors);
|
||||
}
|
||||
|
||||
public Iterable<Contributor> getAllContributors() {
|
||||
return Iterables.concat(authors, contributors);
|
||||
public Stream<Contributor> allContributors() {
|
||||
return Stream.concat(authors.stream(), contributors.stream());
|
||||
}
|
||||
|
||||
public boolean isAuthor(PlayerId player) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.util.logging.Logger;
|
|||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import tc.oc.api.maps.MapUpdateMultiResponse;
|
||||
import tc.oc.api.message.types.UpdateMultiResponse;
|
||||
|
||||
public interface MapLibrary {
|
||||
|
||||
|
@ -44,7 +44,7 @@ public interface MapLibrary {
|
|||
|
||||
Collection<PGMMap> getDirtyMaps();
|
||||
|
||||
ListenableFuture<MapUpdateMultiResponse> pushAllMaps();
|
||||
ListenableFuture<UpdateMultiResponse> pushAllMaps();
|
||||
|
||||
ListenableFuture<MapUpdateMultiResponse> pushDirtyMaps();
|
||||
ListenableFuture<UpdateMultiResponse> pushDirtyMaps();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
@ -23,12 +24,14 @@ import com.google.common.collect.SetMultimap;
|
|||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import tc.oc.api.docs.virtual.UserDoc;
|
||||
import tc.oc.api.exceptions.NotFound;
|
||||
import tc.oc.api.maps.MapService;
|
||||
import tc.oc.api.maps.MapUpdateMultiResponse;
|
||||
import tc.oc.minecraft.scheduler.SyncExecutor;
|
||||
import tc.oc.api.maps.UpdateMapsResponse;
|
||||
import tc.oc.api.message.types.UpdateMultiResponse;
|
||||
import tc.oc.commons.core.logging.Loggers;
|
||||
import tc.oc.commons.core.util.Pair;
|
||||
import tc.oc.commons.core.stream.Collectors;
|
||||
import tc.oc.commons.core.util.SystemFutureCallback;
|
||||
import tc.oc.minecraft.scheduler.SyncExecutor;
|
||||
|
||||
@Singleton
|
||||
public class MapLibraryImpl implements MapLibrary {
|
||||
|
@ -173,66 +176,73 @@ public class MapLibraryImpl implements MapLibrary {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<MapUpdateMultiResponse> pushAllMaps() {
|
||||
public ListenableFuture<UpdateMultiResponse> pushAllMaps() {
|
||||
return pushMaps(getMaps());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<MapUpdateMultiResponse> pushDirtyMaps() {
|
||||
public ListenableFuture<UpdateMultiResponse> pushDirtyMaps() {
|
||||
return pushMaps(getDirtyMaps());
|
||||
}
|
||||
|
||||
private ListenableFuture<MapUpdateMultiResponse> pushMaps(final Collection<PGMMap> maps) {
|
||||
private ListenableFuture<UpdateMultiResponse> pushMaps(final Collection<PGMMap> maps) {
|
||||
final Set<PGMMap> pushedMaps = ImmutableSet.copyOf(maps);
|
||||
logger.info("Pushing " + pushedMaps.size() + " maps");
|
||||
|
||||
final SettableFuture<MapUpdateMultiResponse> future = SettableFuture.create();
|
||||
final SetMultimap<UUID, PGMMap> mapsByContributorId = maps.stream().collect(Collectors.indexingByMulti(
|
||||
map -> map.getInfo()
|
||||
.allContributors()
|
||||
.map(Contributor::getUuid)
|
||||
.filter(Objects::nonNull)
|
||||
));
|
||||
|
||||
logger.info("Pushing " + pushedMaps.size() +
|
||||
" maps and resolving " + mapsByContributorId.keySet().size() + " contributor names");
|
||||
|
||||
final UpdateMapsResponse response = mapService.updateMaps(
|
||||
Collections2.transform(pushedMaps, PGMMap::getDocument)
|
||||
);
|
||||
|
||||
response.authors().forEach(
|
||||
(uuid, future) -> syncExecutor.callback(
|
||||
future,
|
||||
SystemFutureCallback.<UserDoc.Identity>onSuccess(
|
||||
user -> {
|
||||
logger.fine(() -> "Resolved map author " + uuid + " to " + user.username());
|
||||
mapsByContributorId.removeAll(uuid).forEach(
|
||||
map -> map.getInfo().allContributors().forEach(contributor -> {
|
||||
if(uuid.equals(contributor.getUuid())) {
|
||||
contributor.setUser(user);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
).onFailure(NotFound.class, ex ->
|
||||
mapsByContributorId.removeAll(uuid).forEach(
|
||||
map -> map.getLogger().severe("Contributor UUID not found: " + uuid)
|
||||
)
|
||||
).onCompletion(() -> {
|
||||
if(mapsByContributorId.isEmpty()) {
|
||||
logger.info("Finished resolving map contributors");
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
final SettableFuture<UpdateMultiResponse> future = SettableFuture.create();
|
||||
syncExecutor.callback(
|
||||
mapService.updateMapsAndLookupAuthors(Collections2.transform(pushedMaps, PGMMap::getDocument)),
|
||||
new SystemFutureCallback<MapUpdateMultiResponse>() {
|
||||
@Override public void onSuccessThrows(MapUpdateMultiResponse result) throws Exception {
|
||||
logger.info("Push complete: " + result);
|
||||
logErrors(result);
|
||||
response.maps(),
|
||||
SystemFutureCallback.<UpdateMultiResponse>onSuccess(mapResponse -> {
|
||||
logger.info("Push complete: " + mapResponse);
|
||||
logErrors(mapResponse);
|
||||
pushedMaps.forEach(PGMMap::markPushed);
|
||||
resolveContributors(pushedMaps, result.users_by_uuid);
|
||||
future.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
super.onFailure(e);
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
future.set(mapResponse);
|
||||
}).onFailure(Throwable.class, future::setException)
|
||||
);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private void resolveContributors(Collection<PGMMap> maps, Map<UUID, UserDoc.Identity> usersByUuid) {
|
||||
Set<Pair<PGMMap, Contributor>> missing = new HashSet<>();
|
||||
|
||||
for(PGMMap map : maps) {
|
||||
for(Contributor contributor : map.getInfo().getAllContributors()) {
|
||||
if(contributor.needsLookup()) {
|
||||
final UserDoc.Identity user = usersByUuid.get(contributor.getUuid());
|
||||
if(user != null) {
|
||||
contributor.setUser(user);
|
||||
} else {
|
||||
missing.add(Pair.create(map, contributor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Resolved " + usersByUuid.size() + " contributor UUIDs");
|
||||
|
||||
for(Pair<PGMMap, Contributor> pair : missing) {
|
||||
pair.first.getLogger().severe("Contributor UUID not found: " + pair.second.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
private void logErrors(MapUpdateMultiResponse response) {
|
||||
private void logErrors(UpdateMultiResponse response) {
|
||||
for(Map.Entry<String, Map<String, List<String>>> mapEntry : response.errors.entrySet()) {
|
||||
final PGMMap map = needMapById(mapEntry.getKey());
|
||||
for(Map.Entry<String, List<String>> propEntry : mapEntry.getValue().entrySet()) {
|
||||
|
|
|
@ -68,6 +68,6 @@ public abstract class EventHandlerScanner<Event, HandlerAnnotation extends Annot
|
|||
public SetMultimap<EventKey<? extends Event>, Info> findEventHandlers(Class<? extends Listener> listener) {
|
||||
return findEventHandlerMethods(listener)
|
||||
.map(method -> createHandlerInfo(method, findEventType(method), findAnnotation(method)))
|
||||
.collect(Collectors.toSetMultimap(EventSubscriber::key));
|
||||
.collect(Collectors.toImmutableSetMultimap(EventSubscriber::key));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ public class CommandFutureCallback<T> extends SystemFutureCallback<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onFailureUnhandled(Throwable e) {
|
||||
protected void handleDefaultFailure(Throwable e) {
|
||||
exceptionHandlerFactory.create(audience, command)
|
||||
.handleException(e, this, creationSite);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
import tc.oc.commons.core.util.Pair;
|
||||
import tc.oc.commons.core.util.ThrowingFunction;
|
||||
|
||||
public final class FutureUtils {
|
||||
private FutureUtils() {}
|
||||
|
@ -85,15 +86,24 @@ public final class FutureUtils {
|
|||
/**
|
||||
* Equivalent to {@link Futures#transform}, but can be passed a lambda unambiguously
|
||||
*/
|
||||
public static <T, R> ListenableFuture<R> mapSync(ListenableFuture<T> in, Function<T, R> op) {
|
||||
return Futures.transform(in, op::apply);
|
||||
public static <T, R> ListenableFuture<R> mapSync(ListenableFuture<T> in, ThrowingFunction<T, R, ?> op) {
|
||||
return mapSync(in, op, MoreExecutors.sameThreadExecutor());
|
||||
}
|
||||
public static <T, R> ListenableFuture<R> mapSync(ListenableFuture<T> in, Function<T, R> op, Executor executor) {
|
||||
return Futures.transform(in, op::apply, executor);
|
||||
|
||||
public static <T, R> ListenableFuture<R> mapSync(ListenableFuture<T> in, ThrowingFunction<T, R, ?> op, Executor executor) {
|
||||
return mapAsync(in, out -> {
|
||||
try {
|
||||
return Futures.immediateFuture(op.applyThrows(out));
|
||||
} catch(Throwable ex) {
|
||||
return Futures.immediateFailedFuture(ex);
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
|
||||
public static <T, R> ListenableFuture<R> mapAsync(ListenableFuture<T> in, AsyncFunction<T, R> op) {
|
||||
return Futures.transform(in, op);
|
||||
}
|
||||
|
||||
public static <T, R> ListenableFuture<R> mapAsync(ListenableFuture<T> in, AsyncFunction<T, R> op, Executor executor) {
|
||||
return Futures.transform(in, op, executor);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tc.oc.commons.core.stream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -11,14 +12,17 @@ import java.util.function.BinaryOperator;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import tc.oc.commons.core.random.Entropy;
|
||||
import tc.oc.commons.core.random.MutableEntropy;
|
||||
import tc.oc.commons.core.util.AmbiguousElementException;
|
||||
|
@ -99,25 +103,21 @@ public final class Collectors {
|
|||
};
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toListMultimap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) {
|
||||
return new MultimapCollector<>(ImmutableListMultimap::builder, keyMapper, valueMapper);
|
||||
public static <T, K> Collector<T, ?, ImmutableSetMultimap<K, T>> toImmutableSetMultimap(Function<? super T, ? extends K> keyMapper) {
|
||||
return toImmutableSetMultimap(keyMapper, identity());
|
||||
}
|
||||
|
||||
public static <T, K> Collector<T, ?, ImmutableSetMultimap<K, T>> toSetMultimap(Function<? super T, ? extends K> keyMapper) {
|
||||
return toSetMultimap(keyMapper, identity());
|
||||
public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) {
|
||||
return new ImmutableMultimapCollector<>(ImmutableSetMultimap::builder, t -> Stream.of(keyMapper.apply(t)), t -> Stream.of(valueMapper.apply(t)));
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toSetMultimap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) {
|
||||
return new MultimapCollector<>(ImmutableSetMultimap::builder, keyMapper, valueMapper);
|
||||
}
|
||||
|
||||
private static class MultimapCollector<T, K, V, A extends ImmutableMultimap.Builder<K, V>, R extends ImmutableMultimap<K, V>> implements Collector<T, A, R> {
|
||||
private static class ImmutableMultimapCollector<T, K, V, A extends ImmutableMultimap.Builder<K, V>, R extends ImmutableMultimap<K, V>> implements Collector<T, A, R> {
|
||||
|
||||
private final Supplier<A> builderSupplier;
|
||||
private final Function<? super T, ? extends K> keyMapper;
|
||||
private final Function<? super T, ? extends V> valueMapper;
|
||||
private final Function<? super T, Stream<? extends K>> keyMapper;
|
||||
private final Function<? super T, Stream<? extends V>> valueMapper;
|
||||
|
||||
private MultimapCollector(Supplier<A> builderSupplier, Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) {
|
||||
private ImmutableMultimapCollector(Supplier<A> builderSupplier, Function<? super T, Stream<? extends K>> keyMapper, Function<? super T, Stream<? extends V>> valueMapper) {
|
||||
this.builderSupplier = builderSupplier;
|
||||
this.keyMapper = keyMapper;
|
||||
this.valueMapper = valueMapper;
|
||||
|
@ -131,11 +131,11 @@ public final class Collectors {
|
|||
@Override
|
||||
public BiConsumer<A, T> accumulator() {
|
||||
return (builder, t) -> {
|
||||
final K key = keyMapper.apply(t);
|
||||
final V value = valueMapper.apply(t);
|
||||
if(key != null && value != null) {
|
||||
builder.put(key, value);
|
||||
}
|
||||
keyMapper.apply(t).forEach(
|
||||
key -> valueMapper.apply(t).forEach(
|
||||
value -> builder.put(key, value)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -160,6 +160,61 @@ public final class Collectors {
|
|||
}
|
||||
}
|
||||
|
||||
public static <T, K> Collector<T, ?, SetMultimap<K, T>> indexingByMulti(Function<? super T, Stream<? extends K>> keyMapper) {
|
||||
return new MultimapCollector<>(HashMultimap::create, keyMapper, Stream::of);
|
||||
}
|
||||
|
||||
public static <T, V> Collector<T, ?, SetMultimap<T, V>> mappingToMulti(Function<? super T, Stream<? extends V>> valueMapper) {
|
||||
return new MultimapCollector<>(HashMultimap::create, Stream::of, valueMapper);
|
||||
}
|
||||
|
||||
private static class MultimapCollector<T, K, V, A extends Multimap> implements Collector<T, A, A> {
|
||||
|
||||
private final Supplier<A> multimapSupplier;
|
||||
private final Function<? super T, Stream<? extends K>> keyMapper;
|
||||
private final Function<? super T, Stream<? extends V>> valueMapper;
|
||||
|
||||
private MultimapCollector(Supplier<A> multimapSupplier, Function<? super T, Stream<? extends K>> keyMapper, Function<? super T, Stream<? extends V>> valueMapper) {
|
||||
this.multimapSupplier = multimapSupplier;
|
||||
this.keyMapper = keyMapper;
|
||||
this.valueMapper = valueMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<A> supplier() {
|
||||
return multimapSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiConsumer<A, T> accumulator() {
|
||||
return (multimap, t) -> {
|
||||
keyMapper.apply(t).forEach(
|
||||
key -> valueMapper.apply(t).forEach(
|
||||
value -> multimap.put(key, value)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryOperator<A> combiner() {
|
||||
return (m1, m2) -> {
|
||||
m1.putAll(m2);
|
||||
return m1;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<A, A> finisher() {
|
||||
return Function.identity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Characteristics> characteristics() {
|
||||
return Collections.singleton(Characteristics.IDENTITY_FINISH);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListCollector<T, R> implements Collector<T, ArrayList<T>, R> {
|
||||
|
||||
private final Function<ArrayList<T>, R> finisher;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package tc.oc.commons.core.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
@ -25,6 +27,7 @@ public class SystemFutureCallback<T> implements FutureCallback<T> {
|
|||
protected final StackTrace creationSite;
|
||||
private final @Nullable ThrowingConsumer<? super T, ?> successHandler;
|
||||
private final ListMultimap<Class<? extends Throwable>, ThrowingConsumer> failureHandlers = ArrayListMultimap.create();
|
||||
private final List<ThrowingRunnable<?>> completionHandlers = new ArrayList<>();
|
||||
|
||||
public static <T> SystemFutureCallback<T> onSuccess(ThrowingConsumer<? super T, ?> handler) {
|
||||
return new SystemFutureCallback<>(checkNotNull(handler));
|
||||
|
@ -53,6 +56,11 @@ public class SystemFutureCallback<T> implements FutureCallback<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public SystemFutureCallback<T> onCompletion(ThrowingRunnable<Throwable> handler) {
|
||||
completionHandlers.add(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #SystemFutureCallback(ThrowingConsumer)}
|
||||
*/
|
||||
|
@ -68,16 +76,22 @@ public class SystemFutureCallback<T> implements FutureCallback<T> {
|
|||
onSuccessThrows(result);
|
||||
}
|
||||
} catch(Throwable e) {
|
||||
onFailure(e);
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onFailureUnhandled(Throwable e) {
|
||||
exceptionHandler.handleException(e, this, creationSite);
|
||||
handleCompletion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
handleFailure(e);
|
||||
handleCompletion();
|
||||
}
|
||||
|
||||
protected void handleDefaultFailure(Throwable e) {
|
||||
exceptionHandler.handleException(e, this, creationSite);
|
||||
}
|
||||
|
||||
private void handleFailure(Throwable e) {
|
||||
boolean handled = false;
|
||||
for(Map.Entry<Class<? extends Throwable>, ThrowingConsumer> handler : failureHandlers.entries()) {
|
||||
if(handler.getKey().isInstance(e)) {
|
||||
|
@ -85,12 +99,22 @@ public class SystemFutureCallback<T> implements FutureCallback<T> {
|
|||
handler.getValue().acceptThrows(e);
|
||||
handled = true;
|
||||
} catch(Throwable e1) {
|
||||
onFailureUnhandled(e1);
|
||||
handleDefaultFailure(e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!handled) {
|
||||
onFailureUnhandled(e);
|
||||
handleDefaultFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCompletion() {
|
||||
for(ThrowingRunnable<?> handler : completionHandlers) {
|
||||
try {
|
||||
handler.runThrows();
|
||||
} catch(Throwable e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue