package org.expeditee.gui.management; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FrameUtils; import org.expeditee.items.Picture; import org.expeditee.items.Text; import org.expeditee.network.FrameShare; import org.expeditee.setting.DirectoryListSetting; import org.expeditee.settings.folders.FolderSettings; public class ResourceManager { private static boolean commonInvalidated = false; private static ResourceManager instance = new ResourceManager(); protected static ResolvedDirectoryList frames = instance.new ResolvedDirectoryList(FolderSettings.FrameDirs); protected static ResolvedDirectoryList images = instance.new ResolvedDirectoryList(FolderSettings.ImageDirs); protected static ResolvedDirectoryList audio = instance.new ResolvedDirectoryList(FolderSettings.AudioDirs); /** * Creates a Frame object from an associated resour * @param frameName * @param knownPath * @param ignoreAnnotationsOnParse * @return */ public static Frame getExpediteeFrame(String frameName, String knownPath, boolean ignoreAnnotationsOnParse) { Frame loaded = null; if (knownPath != null) { return FrameIO.LoadKnownPath(knownPath, frameName); } else { List canditateDirectoriesForResource = frames.getDirectories(); for (String directoryStr: canditateDirectoriesForResource) { File directory = new File(directoryStr); if (directory.exists()) { loaded = FrameIO.LoadKnownPath(directoryStr, frameName); if (loaded != null) { break; } } } } if (loaded == null && FrameShare.getInstance() != null) { loaded = FrameShare.getInstance().loadFrame(frameName, knownPath); } if (loaded != null) { FrameUtils.Parse(loaded, true, ignoreAnnotationsOnParse); FrameIO.setSavedProperties(loaded); } return loaded; } /** * Creates a picture object from the information stored in the given Text * object. * @param source * The Text file containing the information for sourcing and displaying the information. * @param tryRemote * True if the FrameShare should be consulted to source the image after local locations * have failed to find the corresponding file. * @return */ public static Picture getExpediteePicture(Text source, boolean tryRemote) { // Extract useful content from source text. String content = source.getText().replaceFirst("@i:", ""); String path = ""; String size = ""; String fileName = ""; content = content.replaceAll("\n", ""); content = content.trim(); // Image file names must contain a '.' to separate the file name from extension. int fileSuffixCharIndex = content.indexOf('.'); if (fileSuffixCharIndex < 0) { return null; } // Separate the path to resource file from additional information. int endOfFileNameIndex = content.indexOf(' ', fileSuffixCharIndex); if (endOfFileNameIndex < 0) { path = content; size = ""; } else { path = content.substring(0, endOfFileNameIndex); size = content.substring(endOfFileNameIndex).trim(); } fileName = path; // Redacted images are generated in memory as noise rather than being // associated with a specific resource on the file system. Therefore, // no more work is required. if (fileName.equals(Picture.REDACTED_IMAGE_NAME)) { return new Picture(source, fileName, path, size); } // Having gotten to this point, we now have to find the resource described // by this Expeditee Text Item. List canditateDirectoriesForResource = images.getDirectories(source); canditateDirectoriesForResource.add(FrameIO.PARENT_FOLDER); // First try finding the resource in the directories specified by the // users settings frame. File resourceFile = null; for (String dirStr: canditateDirectoriesForResource) { File dir = new File(dirStr); if (dir.exists()) { File checkResourceFile = new File(dirStr + path); if (checkResourceFile.exists() && !checkResourceFile.isDirectory()) { resourceFile = checkResourceFile; break; } } } // At this point, if resourceFile is pointing to a file object that // either doesn't exist or is a directory, then we have not been able // to find the correct file. // // If the correct file is out there then two more possibilities: // a. The text Item is specifying an absolute path or, // b. The text Item is specifying a path relative to this java project. // These are of the format "/packageA/packageB/filename". For // example: "/org/expeditee/gui.management/ResourceManager.java" if (resourceFile == null/* || !resourceFile.exists() || resourceFile.isDirectory()*/) { // Prepare for case a. resourceFile = new File(path); } if (!resourceFile.exists() || resourceFile.isDirectory()) { // We are not looking at an absolute path so check option b. URL resourceFileURL = ResourceManager.class.getResource(path); // Decode to remove %20 in Windows folder names if (resourceFileURL != null) { try { path = URLDecoder.decode(resourceFileURL.getFile(), "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } else { // We are looking at an absolute path, option a. path = resourceFile.getPath(); } // At this point, the resource has not been found locally. One last // option is that the FrameShare might be able to help us. resourceFile = new File(path); if (!resourceFile.exists() || resourceFile.isDirectory()) { if (tryRemote && FrameShare.getInstance().loadImage(fileName, null)) { // Recursive call, but with try remote as false. // The above call to the FrameShare should have gotten us a local copy. return getExpediteePicture(source, false); } else { return null; } } // If we have gotten here, we should have found the correct resourceFile return new Picture(source, fileName, path, size); } /** * Gets the audio file named 'localFileName' on the file system. Searches * the locations listed in the users settings frame. * @param localFileName * @param parentFrame * @return */ public static File getAudioResource(String localFileName, Frame parentFrame) { List directories = audio.getDirectories(parentFrame); directories.add(FrameIO.PARENT_FOLDER); for (String directoryStr: directories) { File directory = new File(directoryStr); if (directory.exists()) { Path directoryPath = Paths.get(directoryStr); Path pathToFile = directoryPath.resolve(localFileName); File file = pathToFile.toFile(); if (file.exists() && !file.isDirectory()) { return file; } } } return null; } /** * Gets all the audio files that match the given predicate 'includeFile'. * @param includeFile * @param parentFrame * @return */ public static List gatherAudioResources(Predicate includeFile, Frame parentFrame) { List directories = audio.getDirectories(parentFrame); directories.add(FrameIO.PARENT_FOLDER); List results = new ArrayList(); for (String directory: directories) { File dir = new File(directory); if (dir.exists()) { File[] canditates = dir.listFiles(); for (File f: canditates) { if (!f.isDirectory() && includeFile.test(f)) { results.add(f); } } } } return results; } public static void invalidateAllResourceDirectories() { commonInvalidated = true; } private static void refreshAll() { Frame currentFrame = DisplayController.getCurrentFrame(); String framesetPath = currentFrame == null ? null : currentFrame.getFramesetPath(); frames.refresh(framesetPath); images.refresh(framesetPath); audio.refresh(framesetPath); } protected class ResolvedDirectoryList { private List directories = null; private String contextFramesetPath = null; private DirectoryListSetting source; public ResolvedDirectoryList(DirectoryListSetting source) { this.source = source; } public List getDirectories(Text context) { return getDirectories(context.getParentOrCurrentFrame()); } public List getDirectories(Frame context) { String contextPath = context == null ? null : context.getFramesetPath(); return getDirectories(contextPath); } public List getDirectories() { return getDirectories((String) null); } private List getDirectories(String context) { if (commonInvalidated) { ResourceManager.refreshAll(); } else { boolean requiresRefresh = context == null || contextFramesetPath == null || !contextFramesetPath.equals(context); if (requiresRefresh) { refresh(context); } } return directories; } private void refresh(String context) { List unresolved = source.getAbsoluteDirs(); directories = resolve(unresolved, context); contextFramesetPath = context; } private List resolve(List unresolved, String context) { if (context == null) { return resolveWithNullContext(unresolved); } List resolved = new ArrayList(); if (unresolved == null) { return resolved; } Path expediteeHome = Paths.get(FrameIO.PARENT_FOLDER).toAbsolutePath(); Path absolutePath = Paths.get(context).toAbsolutePath(); Path p = expediteeHome.relativize(absolutePath); for (String s: unresolved) { String local = ResourceUtil.substitute(s, ResourceUtil.CURRENT_FRAMESET_FLAG, p.toString()); resolved.add(local); } return resolved; } private List resolveWithNullContext(List unresolved) { List resolved = new ArrayList(); if (unresolved == null) { return resolved; } for (String s: unresolved) { String local = ResourceUtil.substitute(s, ResourceUtil.CURRENT_FRAMESET_FLAG, "ERROR."); if (s.equals(local)) { resolved.add(local); } } return resolved; } } }