package io.gitlab.jfronny.respackopts.filters; import io.gitlab.jfronny.libjf.ResourcePath; import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents; import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.filters.util.DirRpoResult; import io.gitlab.jfronny.respackopts.gson.AttachmentHolder; import io.gitlab.jfronny.respackopts.model.DirRpo; import io.gitlab.jfronny.respackopts.model.cache.CachedPackState; import io.gitlab.jfronny.respackopts.model.enums.PackCapability; import io.gitlab.jfronny.respackopts.util.MetaCache; import net.minecraft.resource.*; import net.minecraft.util.Identifier; import java.io.*; import java.util.*; public enum DirFilterEvents implements UserResourceEvents.Open, UserResourceEvents.FindResource { INSTANCE; public static void init() { UserResourceEvents.OPEN.register(INSTANCE); UserResourceEvents.FIND_RESOURCE.register(INSTANCE); } @Override public InputSupplier open(ResourceType type, Identifier id, InputSupplier previous, ResourcePack pack) { if (!MetaCache.hasCapability(pack, PackCapability.DirFilter)) return previous; String path = new ResourcePath(type, id).getName(); List rpo = findDirRpos(pack, parent(path)); //TODO use pattern matching for switch DirRpoResult result = DirRpoResult.compute(path, rpo, MetaCache.getKeyByPack(pack)); if (result == DirRpoResult.ORIGINAL) return previous; // Using original file if (result == DirRpoResult.IGNORE) return null; // No fallback // Use fallback DirRpoResult.Replacement replacement = (DirRpoResult.Replacement) result; ResourcePath rp = new ResourcePath(replacement.toFallback(path)); return pack.open(rp.getType(), rp.getId()); } @Override public ResourcePack.ResultConsumer findResources(ResourceType type, String namespace, String prefix, ResourcePack.ResultConsumer previous, ResourcePack pack) { // Warning: the Identifiers here DON'T CONTAIN THE TYPE! // Therefore, it needs to be added when calling a method that generates a ResourcePath! if (!MetaCache.hasCapability(pack, PackCapability.DirFilter)) return previous; boolean dirFilterAdditive = MetaCache.hasCapability(pack, PackCapability.DirFilterAdditive); String searchPrefix = type.getDirectory() + "/" + namespace + "/" + prefix; Set additionalSearched = new HashSet<>(); return (identifier, value) -> { String path = path(type, identifier); //TODO use pattern matching for switch List relevantRpos = findDirRpos(pack, parent(path)); DirRpoResult result = DirRpoResult.compute(path, relevantRpos, MetaCache.getKeyByPack(pack)); if (result == DirRpoResult.ORIGINAL) { // Using original file previous.accept(identifier, value); return; } if (result == DirRpoResult.IGNORE) return; // No fallback // New search for fallback path DirRpoResult.Replacement replacement = (DirRpoResult.Replacement) result; String newPath = replacement.toFallback(path); if (newPath.split("/", 3).length != 3) { Respackopts.LOGGER.error("Directory fallback path MUST be long enough to support representation as identifier (3 segments), but is too short: " + newPath); return; } if (!dirFilterAdditive) { // Only return this single result, don't search for others ResourcePath rp = new ResourcePath(newPath); previous.accept(identifier, pack.open(rp.getType(), rp.getId())); return; } // Find other files in the fallback directory String fallbackDir = replacement.fallback.prefix(); if (!additionalSearched.add(fallbackDir)) return; // Already searched int prefixSize = replacement.original.prefix().length(); if (prefixSize < searchPrefix.length()) { if (!searchPrefix.startsWith(replacement.original.prefix())) { Respackopts.LOGGER.error("Unexpected prefix path " + replacement.original.prefix() + " for search prefix " + searchPrefix + ", skipping"); return; } fallbackDir += searchPrefix.substring(prefixSize); } else if (!replacement.original.prefix().startsWith(searchPrefix)) { Respackopts.LOGGER.error("Unexpected prefix path " + replacement.original.prefix() + " for search prefix " + searchPrefix + ", skipping"); return; } if (fallbackDir.split("/", 3).length != 3) { Respackopts.LOGGER.error("Directory fallback path MUST be long enough to support representation as identifier (3 segments), but is too short: " + fallbackDir); return; } ResourcePath rp = new ResourcePath(fallbackDir); pack.findResources(rp.getType(), rp.getId().getNamespace(), rp.getId().getPath(), (resource, resVal) -> { String fallbackPath = path(rp.getType(), resource); previous.accept(new ResourcePath(replacement.toOriginal(fallbackPath)).getId(), resVal); }); }; } private String path(ResourceType type, Identifier identifier) { return type.getDirectory() + "/" + identifier.getNamespace() + "/" + identifier.getPath(); } private String parent(String path) { int li = path.lastIndexOf('/'); return li <= 0 ? null : path.substring(0, li); } /** * Identify all directory RPOs relevant to the directory at {@code path} (IE in it or its parents), from outermost to innermost */ private List findDirRpos(ResourcePack pack, String path) { if (path == null) return List.of(); CachedPackState state = MetaCache.getState(MetaCache.getKeyByPack(pack)); var cache = state.cachedDirRPOs(); { // This is outside computeIfAbsent because it could cause modification of the map, which is unsupported within it List cached = cache.get(path); if (cached != null) return cached; } List parentRPOs = findDirRpos(pack, parent(path)); synchronized (cache) { // This is synchronized as multiple resources might be accessed at the same time, potentially causing a CME here return cache.computeIfAbsent(path, $ -> { ResourcePath rp; try { rp = new ResourcePath(path + "/" + Respackopts.FILE_EXTENSION); } catch (Exception e) { return parentRPOs; } InputSupplier is = UserResourceEvents.disable(() -> pack.open(rp.getType(), rp.getId())); if (is == null) return parentRPOs; try (Reader w = new InputStreamReader(is.get())) { List currentRPOs = new LinkedList<>(parentRPOs); DirRpo newRPO = AttachmentHolder.deserialize(state.metadata().version, w, DirRpo.class); newRPO.hydrate(path); if (newRPO.fallback != null && !newRPO.fallback.endsWith("/")) newRPO.fallback += "/"; currentRPOs.add(newRPO); return currentRPOs; } catch (IOException e) { Respackopts.LOGGER.error("Couldn't open dir rpo " + rp.getName(), e); } return parentRPOs; }); } } }