2017-01-30 01:43:34 +01:00
|
|
|
package tc.oc.pgm.map;
|
|
|
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2017-02-02 09:51:51 +01:00
|
|
|
import java.util.Objects;
|
2017-01-30 01:43:34 +01:00
|
|
|
import java.util.Optional;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.UUID;
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import javax.inject.Singleton;
|
|
|
|
|
|
|
|
import com.google.common.collect.Collections2;
|
|
|
|
import com.google.common.collect.HashMultimap;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
|
|
import com.google.common.collect.Lists;
|
|
|
|
import com.google.common.collect.Maps;
|
|
|
|
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;
|
2017-02-02 09:51:51 +01:00
|
|
|
import tc.oc.api.exceptions.NotFound;
|
2017-01-30 01:43:34 +01:00
|
|
|
import tc.oc.api.maps.MapService;
|
2017-02-02 09:51:51 +01:00
|
|
|
import tc.oc.api.maps.UpdateMapsResponse;
|
|
|
|
import tc.oc.api.message.types.UpdateMultiResponse;
|
2017-01-30 01:43:34 +01:00
|
|
|
import tc.oc.commons.core.logging.Loggers;
|
2017-02-02 09:51:51 +01:00
|
|
|
import tc.oc.commons.core.stream.Collectors;
|
2017-01-30 01:43:34 +01:00
|
|
|
import tc.oc.commons.core.util.SystemFutureCallback;
|
2017-02-02 09:51:51 +01:00
|
|
|
import tc.oc.minecraft.scheduler.SyncExecutor;
|
2017-01-30 01:43:34 +01:00
|
|
|
|
|
|
|
@Singleton
|
|
|
|
public class MapLibraryImpl implements MapLibrary {
|
|
|
|
|
|
|
|
private final SyncExecutor syncExecutor;
|
|
|
|
private final MapService mapService;
|
|
|
|
|
|
|
|
protected final Map<MapId, PGMMap> mapsById = Maps.newHashMap();
|
|
|
|
protected final Map<Path, PGMMap> mapsByPath = new HashMap<>();
|
|
|
|
protected final SetMultimap<String, PGMMap> mapsByName = HashMultimap.create();
|
|
|
|
protected final Logger logger;
|
|
|
|
|
|
|
|
@Inject MapLibraryImpl(Loggers loggers, SyncExecutor syncExecutor, MapService mapService) {
|
|
|
|
this.syncExecutor = syncExecutor;
|
|
|
|
this.mapService = mapService;
|
|
|
|
this.logger = loggers.get(getClass());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Set<PGMMap> addMaps(Collection<PGMMap> maps) {
|
|
|
|
Set<PGMMap> added = new HashSet<>();
|
|
|
|
for(PGMMap map : maps) {
|
|
|
|
if(addMap(map)) added.add(map);
|
|
|
|
}
|
|
|
|
return added;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean addMap(PGMMap map) {
|
|
|
|
final MapId id = map.getId();
|
|
|
|
PGMMap old = mapsById.get(id);
|
|
|
|
|
|
|
|
if(old == null) {
|
|
|
|
logger.fine("Adding " + id);
|
|
|
|
} else if(old.getSource().hasPriorityOver(map.getSource())) {
|
|
|
|
logger.fine("Skipping duplicate " + id);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
logger.fine("Replacing duplicate " + id);
|
|
|
|
}
|
|
|
|
|
|
|
|
mapsById.put(id, map);
|
|
|
|
mapsByPath.put(map.getFolder().getAbsolutePath(), map);
|
|
|
|
mapsByName.put(map.getName(), map);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void removeMaps(Collection<Path> paths) {
|
|
|
|
for(Path path : paths) removeMap(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean removeMap(Path path) {
|
|
|
|
PGMMap map = mapsByPath.remove(path);
|
|
|
|
if(map == null) return false;
|
|
|
|
|
|
|
|
mapsById.remove(map.getId());
|
|
|
|
mapsByName.remove(map.getName(), map);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Logger getLogger() {
|
|
|
|
return this.logger;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Collection<PGMMap> getMaps() {
|
|
|
|
return this.mapsById.values();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Set<String> getMapNames() {
|
|
|
|
return mapsByName.keySet();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Map<Path, PGMMap> getMapsByPath() {
|
|
|
|
return mapsByPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @Nullable PGMMap getMapById(String mapId) {
|
|
|
|
return getMapById(MapId.parse(mapId));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @Nullable PGMMap getMapById(MapId mapId) {
|
|
|
|
return mapsById.get(mapId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public PGMMap needMapById(String mapId) {
|
|
|
|
return needMapById(MapId.parse(mapId));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public PGMMap needMapById(MapId mapId) {
|
|
|
|
final PGMMap map = getMapById(mapId);
|
|
|
|
if(map == null) {
|
|
|
|
throw new IllegalStateException("No map with ID '" + mapId + "'");
|
|
|
|
}
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Optional<PGMMap> getMapByNameOrId(String nameOrId) {
|
|
|
|
Set<PGMMap> maps = mapsByName.get(nameOrId);
|
|
|
|
|
|
|
|
if(maps.isEmpty()) {
|
|
|
|
return Optional.ofNullable(mapsById.get(MapId.parse(nameOrId)));
|
|
|
|
}
|
|
|
|
|
|
|
|
PGMMap best = null;
|
|
|
|
for(PGMMap map : maps) {
|
|
|
|
if(best == null || map.getSource().hasPriorityOver(best.getSource())) {
|
|
|
|
best = map;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Optional.of(best);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<PGMMap> resolveMaps(List<String> namesOrIds) {
|
|
|
|
List<PGMMap> mapResult = Lists.newArrayList();
|
|
|
|
for(String slug : namesOrIds) {
|
|
|
|
Optional<PGMMap> map = this.getMapByNameOrId(slug);
|
|
|
|
if(map.isPresent()) {
|
|
|
|
mapResult.add(map.get());
|
|
|
|
} else {
|
|
|
|
this.logger.warning("Could not find map: " + slug);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mapResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Collection<PGMMap> getDirtyMaps() {
|
|
|
|
return Collections2.filter(getMaps(), (map) -> !map.isPushed());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-02-02 09:51:51 +01:00
|
|
|
public ListenableFuture<UpdateMultiResponse> pushAllMaps() {
|
2017-01-30 01:43:34 +01:00
|
|
|
return pushMaps(getMaps());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-02-02 09:51:51 +01:00
|
|
|
public ListenableFuture<UpdateMultiResponse> pushDirtyMaps() {
|
2017-01-30 01:43:34 +01:00
|
|
|
return pushMaps(getDirtyMaps());
|
|
|
|
}
|
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
private ListenableFuture<UpdateMultiResponse> pushMaps(final Collection<PGMMap> maps) {
|
2017-01-30 01:43:34 +01:00
|
|
|
final Set<PGMMap> pushedMaps = ImmutableSet.copyOf(maps);
|
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
final SetMultimap<UUID, PGMMap> mapsByContributorId = maps.stream().collect(Collectors.indexingByMulti(
|
|
|
|
map -> map.getInfo()
|
|
|
|
.allContributors()
|
|
|
|
.map(Contributor::getUuid)
|
|
|
|
.filter(Objects::nonNull)
|
|
|
|
));
|
2017-01-30 01:43:34 +01:00
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
logger.info("Pushing " + pushedMaps.size() +
|
|
|
|
" maps and resolving " + mapsByContributorId.keySet().size() + " contributor names");
|
2017-01-30 01:43:34 +01:00
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
final UpdateMapsResponse response = mapService.updateMaps(
|
|
|
|
Collections2.transform(pushedMaps, PGMMap::getDocument)
|
|
|
|
);
|
2017-01-30 01:43:34 +01:00
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
2017-01-30 01:43:34 +01:00
|
|
|
}
|
2017-02-02 09:51:51 +01:00
|
|
|
).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");
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
2017-01-30 01:43:34 +01:00
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
final SettableFuture<UpdateMultiResponse> future = SettableFuture.create();
|
|
|
|
syncExecutor.callback(
|
|
|
|
response.maps(),
|
|
|
|
SystemFutureCallback.<UpdateMultiResponse>onSuccess(mapResponse -> {
|
|
|
|
logger.info("Push complete: " + mapResponse);
|
|
|
|
logErrors(mapResponse);
|
|
|
|
pushedMaps.forEach(PGMMap::markPushed);
|
|
|
|
future.set(mapResponse);
|
|
|
|
}).onFailure(Throwable.class, future::setException)
|
|
|
|
);
|
2017-01-30 01:43:34 +01:00
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
return future;
|
2017-01-30 01:43:34 +01:00
|
|
|
}
|
|
|
|
|
2017-02-02 09:51:51 +01:00
|
|
|
private void logErrors(UpdateMultiResponse response) {
|
2017-01-30 01:43:34 +01:00
|
|
|
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()) {
|
|
|
|
for(String message : propEntry.getValue()) {
|
|
|
|
map.getLogger().severe("Error saving map to database: " + propEntry.getKey() + " " + message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|