ProjectAres/PGM/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java

256 lines
8.2 KiB
Java

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;
import java.util.Objects;
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;
import tc.oc.api.exceptions.NotFound;
import tc.oc.api.maps.MapService;
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.stream.Collectors;
import tc.oc.commons.core.util.SystemFutureCallback;
import tc.oc.minecraft.scheduler.SyncExecutor;
@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
public ListenableFuture<UpdateMultiResponse> pushAllMaps() {
return pushMaps(getMaps());
}
@Override
public ListenableFuture<UpdateMultiResponse> pushDirtyMaps() {
return pushMaps(getDirtyMaps());
}
private ListenableFuture<UpdateMultiResponse> pushMaps(final Collection<PGMMap> maps) {
final Set<PGMMap> pushedMaps = ImmutableSet.copyOf(maps);
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(
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)
);
return future;
}
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()) {
for(String message : propEntry.getValue()) {
map.getLogger().severe("Error saving map to database: " + propEntry.getKey() + " " + message);
}
}
}
}
}