/** * FrameIO.java * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.expeditee.gui; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.sql.Time; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; import org.expeditee.actions.Actions; import org.expeditee.agents.ExistingFramesetException; import org.expeditee.agents.InvalidFramesetNameException; import org.expeditee.auth.AuthenticatorBrowser; import org.expeditee.encryption.io.EncryptedExpReader; import org.expeditee.encryption.io.EncryptedExpWriter; import org.expeditee.gio.EcosystemManager; import org.expeditee.gui.management.ProfileManager; import org.expeditee.gui.management.ResourceManager; import org.expeditee.io.Conversion; import org.expeditee.io.ExpReader; import org.expeditee.io.ExpWriter; import org.expeditee.io.FrameReader; import org.expeditee.io.FrameWriter; import org.expeditee.io.KMSReader; import org.expeditee.io.KMSWriter; import org.expeditee.items.Item; import org.expeditee.items.ItemUtils; import org.expeditee.items.Justification; import org.expeditee.items.PermissionTriple; import org.expeditee.items.Text; import org.expeditee.items.UserAppliedPermission; import org.expeditee.network.FrameShare; import org.expeditee.setting.Setting; import org.expeditee.settings.UserSettings; import org.expeditee.settings.folders.FolderSettings; import org.expeditee.settings.templates.TemplateSettings; import org.expeditee.stats.Formatter; import org.expeditee.stats.Logger; import org.expeditee.stats.SessionStats; /** * This class provides static methods for all saving and loading of Frames * to\from disk. This class also handles any caching of previously loaded * Frames. * * @author jdm18 * */ public class FrameIO { private static final char FRAME_NAME_LAST_CHAR = 'A'; // The parent path that all others are relative to. Also referred to as Expeditee Home. public static String PARENT_FOLDER; public static String PROFILE_PATH; public static String FRAME_PATH; public static String IMAGES_PATH; public static String AUDIO_PATH; public static String PUBLIC_PATH; public static String TRASH_PATH; public static String FONT_PATH; public static String DICT_PATH; public static String EXPORTS_PATH; public static String STATISTICS_PATH; public static String LOGS_PATH; public static String MESSAGES_PATH; public static String MAIL_PATH; public static String SHARED_FRAMESETS_PATH; public static String CONTACTS_PATH; public static String HELP_PATH; public static String DEAD_DROPS_PATH; public static String GROUP_PATH; public static String RESOURCES_PRIVATE_PATH; public static String RESOURCES_PATH; public static String FRAME_USERNAME_PRIVATE_PATH; public static String IMAGES_USERNAME_PRIVATE_PATH; public static String AUDIO_USERNAME_PRIVATE_PATH; public static String HELP_USERNAME_PRIVATE_PATH; // Paths that appear to be unused. public static String TEMPLATES_PATH; // Variables for controlling cache functionality. public static final int MAX_NAME_LENGTH = 64; public static final int MAX_CACHE = 100; private static HashMap _Cache = new FrameCache(); private static final boolean ENABLE_CACHE = true; private static boolean _UseCache = true; private static boolean _SuspendedCache = false; private static final String INF_FILENAME = "frame.inf"; public static void changeParentAndSubFolders(String newFolder) { // Partial Paths PARENT_FOLDER = newFolder; PUBLIC_PATH = PARENT_FOLDER + "public" + File.separator; TRASH_PATH = PARENT_FOLDER + "trash" + File.separator; PROFILE_PATH = PARENT_FOLDER + "profiles" + File.separator; EXPORTS_PATH = PARENT_FOLDER + "exports" + File.separator; STATISTICS_PATH = PARENT_FOLDER + "statistics" + File.separator; LOGS_PATH = PARENT_FOLDER + "logs" + File.separator; String resourcesPublicPath = PARENT_FOLDER + "resources-public" + File.separator; String resourcesPrivateUserPath = PARENT_FOLDER + "resources-" + UserSettings.UserName.get() + File.separator; if (UserSettings.PublicAndPrivateResources) { // Paths for the new regime FONT_PATH = resourcesPublicPath + "fonts" + File.separator; DICT_PATH = resourcesPublicPath + "dict" + File.separator; HELP_PATH = resourcesPublicPath + "documentation" + File.separator; HELP_USERNAME_PRIVATE_PATH = resourcesPrivateUserPath + "documentation" + File.separator; FRAME_PATH = resourcesPublicPath + "framesets" + File.separator; FRAME_USERNAME_PRIVATE_PATH = resourcesPrivateUserPath + "framesets" + File.separator; MESSAGES_PATH = resourcesPrivateUserPath + "messages" + File.separator; MAIL_PATH = resourcesPrivateUserPath + "mail" + File.separator; IMAGES_PATH = resourcesPublicPath + "images" + File.separator; IMAGES_USERNAME_PRIVATE_PATH = resourcesPrivateUserPath + "images" + File.separator; AUDIO_PATH = resourcesPublicPath + "audio" + File.separator; AUDIO_USERNAME_PRIVATE_PATH = resourcesPrivateUserPath + "audio" + File.separator; GROUP_PATH = resourcesPrivateUserPath + "groups" + File.separator; // Used only when extracting resources (when expeditee is run for first time) RESOURCES_PRIVATE_PATH = PARENT_FOLDER + "resources-private" + File.separator; if (AuthenticatorBrowser.isAuthenticated()) { // Paths for the new regime while authenticated SHARED_FRAMESETS_PATH = resourcesPrivateUserPath + "framesets-shared" + File.separator; DEAD_DROPS_PATH = resourcesPrivateUserPath + "deaddrops" + File.separator; CONTACTS_PATH = resourcesPrivateUserPath + "contacts" + File.separator; //MAIL_PATH = resourcesPrivateUserPath + "mail" + File.separator; } else { SHARED_FRAMESETS_PATH = null; DEAD_DROPS_PATH = null; CONTACTS_PATH = null; //MAIL_PATH = null; } } else { // Paths for the old regime FONT_PATH = PARENT_FOLDER + "fonts" + File.separator; DICT_PATH = PARENT_FOLDER + "dict" + File.separator; HELP_PATH = PARENT_FOLDER + "documentation" + File.separator; FRAME_PATH = PARENT_FOLDER + "framesets" + File.separator; MESSAGES_PATH = PARENT_FOLDER + "messages" + File.separator; IMAGES_PATH = PARENT_FOLDER + "images" + File.separator; AUDIO_PATH = PARENT_FOLDER + "audio" + File.separator; GROUP_PATH = PARENT_FOLDER + "groups" + File.separator; // These paths are not used by old regime. HELP_USERNAME_PRIVATE_PATH = null; FRAME_USERNAME_PRIVATE_PATH = null; IMAGES_USERNAME_PRIVATE_PATH = null; AUDIO_USERNAME_PRIVATE_PATH = null; // - This last one is never used because old regime is never extracted. If we are going to FrameUtils.extractResources then we are doing new regime. RESOURCES_PRIVATE_PATH = null; if (AuthenticatorBrowser.isAuthenticated()) { // Paths for the old regime while authenticated SHARED_FRAMESETS_PATH = PARENT_FOLDER + "framesets-shared" + File.separator; DEAD_DROPS_PATH = PARENT_FOLDER + "deaddrops" + File.separator; CONTACTS_PATH = PARENT_FOLDER + "contacts" + File.separator; MAIL_PATH = PARENT_FOLDER + "mail" + File.separator; } else { SHARED_FRAMESETS_PATH = null; DEAD_DROPS_PATH = null; CONTACTS_PATH = null; MAIL_PATH = null; } } //System.err.println("**** FrameIO::changeParentAndSubFolder(): Calling AudioPathManger.changeParentAndSubFolder()"); //AudioPathManager.changeParentAndSubFolders(newFolder); } // All methods are static, this should not be instantiated private FrameIO() { } public static boolean isCacheOn() { return _UseCache && ENABLE_CACHE; } public static void Precache(String framename) { // if the cache is turned off, do nothing if (!isCacheOn()) { return; } // if the frame is already in the cache, do nothing if (_Cache.containsKey(framename.toLowerCase())) { return; } // otherwise, load the frame and put it in the cache Logger.Log(Logger.SYSTEM, Logger.LOAD, "Precaching " + framename + "."); // do not display errors encountered to the user // (they will be shown at load time) MessageBay.suppressMessages(true); // loading automatically caches the frame is caching is turned on LoadFromDisk(framename, null, false); MessageBay.suppressMessages(false); } /** * Checks if a string is a representation of a positive integer. * * @param s * @return true if s is a positive integer */ public static boolean isPositiveInteger(String s) { if (s == null || s.length() == 0) { return false; } for (int i = 0; i < s.length(); i++) { if (!Character.isDigit(s.charAt(i))) { return false; } } return true; } /** * Loads a frame with the specified name. * By using a dot separated framename, users are able to specify the path to find the frameset in. * @param frameName The frame to load. * @return the loaded frame */ public static Frame LoadFrame(String frameName) { if (frameName.contains(".")) { String[] split = frameName.split("\\."); String[] pathSplit = Arrays.copyOfRange(split, 0, split.length - 1); String name = split[split.length - 1]; String path = Arrays.asList(pathSplit).stream().collect(Collectors.joining(File.separator)); return LoadFrame(name, Paths.get(FrameIO.PARENT_FOLDER).resolve(path).toString() + File.separator, false); } else { return LoadFrame(frameName, null, false); } } public static Frame LoadFrame(String frameName, String path) { return LoadFrame(frameName, path, false); } public static Frame LoadFrame(String frameName, String path, boolean ignoreAnnotations) { if (!isValidFrameName(frameName)) { return null; } String frameNameLower = frameName.toLowerCase(); // first try reading from cache if (isCacheOn() && _Cache.containsKey(frameNameLower)) { Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName + " from cache."); Frame frame = _Cache.get(frameNameLower); // if frame in cache is older than the one on disk then don't use the cached one File file = new File(frame.getFramePathReal()); long lastModified = file.lastModified(); if (lastModified <= frame.getLastModifyPrecise()) { return frame; } } Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName + " from disk."); Frame fromDisk = LoadFromDisk(frameName, path, ignoreAnnotations); return fromDisk; } //Loads the 'restore' version of a frame if there is one public static Frame LoadRestoreFrame(Frame frameToRestore) { String fullPath = getFrameFullPathName(frameToRestore.getPath(), frameToRestore .getName()); //System.out.println("fullpath: " + fullPath); String restoreVersion = fullPath + ".restore"; //System.out.println("restoreversion" + restoreVersion); File source = new File(restoreVersion); File dest = new File(fullPath); FileChannel inputChannel = null; FileChannel outputChannel = null; try{ FileInputStream source_fis = new FileInputStream(source); inputChannel = source_fis.getChannel(); FileOutputStream dest_fos = new FileOutputStream(dest); outputChannel = dest_fos.getChannel(); outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); inputChannel.close(); outputChannel.close(); source_fis.close(); dest_fos.close(); } catch(Exception e){ System.err.println("No restore point detected."); } String frameName = frameToRestore.getName(); String frameNameLower = frameName.toLowerCase(); // first try reading from cache if (isCacheOn() && _Cache.containsKey(frameNameLower)) { Logger.Log(Logger.SYSTEM, Logger.LOAD, "Clearing " + frameName + " from cache."); _Cache.remove(frameNameLower); } return LoadFrame(frameName, frameToRestore.getPath(), true); } public static BufferedReader LoadPublicFrame(String frameName) { String fullPath = FrameIO.getFrameFullPathName(PUBLIC_PATH, frameName); if (fullPath == null) { return null; } File frameFile = new File(fullPath); if (frameFile.exists() && frameFile.canRead()) { try { return new BufferedReader(new FileReader(frameFile)); } catch (FileNotFoundException e) { e.printStackTrace(); } } return null; } private static Frame LoadFromDisk(String frameName, String knownPath, boolean ignoreAnnotationsOnParse) { return ResourceManager.getExpediteeFrame(frameName, knownPath, ignoreAnnotationsOnParse); } // private static Frame LoadFromDisk(String framename, String knownPath, // boolean ignoreAnnotations) { // Frame loaded = null; // // if (knownPath != null) { // loaded = LoadKnownPath(knownPath, framename); // } else { // List directoriesToSearch = FolderSettings.FrameDirs.getAbsoluteDirs(); // // for (String path : directoriesToSearch) { // loaded = LoadKnownPath(path, framename); // if (loaded != null) { // break; // } // } // } // // if (loaded == null && FrameShare.getInstance() != null) { // loaded = FrameShare.getInstance().loadFrame(framename, knownPath); // } // // if (loaded != null) { // FrameUtils.Parse(loaded, true, ignoreAnnotations); // FrameIO.setSavedProperties(loaded); // } // // return loaded; // } /** * Gets a list of all the framesets available to the user * * @return a string containing a list of all the available framesets on * separate lines */ public static String getFramesetList() { StringBuffer list = new StringBuffer(); for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) { File files = new File(path); if (!files.exists()) { continue; } for (File f : (new File(path)).listFiles()) { if (f.isDirectory()) { list.append(f.getName()).append('\n'); } } } // remove the final new line char list.deleteCharAt(list.length() - 1); return list.toString(); } /** * Gets a list of all the profiles available to the user * * @return a list containing all the available framesets on separate lines */ public static List getProfilesList() { File[] listFiles = new File(FrameIO.PROFILE_PATH).listFiles(); if (listFiles == null) return new ArrayList(); List potentialProfiles = Arrays.asList(listFiles); potentialProfiles.removeIf(file -> !file.isDirectory()); return potentialProfiles.stream().map(dir -> dir.getName()).collect(Collectors.toList()); } /** * Gets the full path and file name of the frame. * This is a alias for Frame::getFramePathLogical * @param path- * the directory in which to look for the frameset containing the * frame. * @param frameName- * the name of the frame for which the path is being requested. * @return null if the frame can not be located. */ public static synchronized String getFrameFullPathName(String path, String frameName) { String source; String fileName = null; if(frameName.contains("restore")){ source = path + File.separator;// + frameName; fileName = path + File.separator + frameName + ExpReader.EXTENTION; } else { source = path + Conversion.getFramesetName(frameName) + File.separator; } File tester = new File(source); if (!tester.exists()) { return null; } String fullPath; if(frameName.contains("restore")){ fullPath = fileName; } else { // check for the new file name format fullPath = source + Conversion.getFrameNumber(frameName) + ExpReader.EXTENTION; } tester = new File(fullPath); if (tester.exists()) { return fullPath; } // check for oldfile name format fullPath = source + Conversion.getFramesetName(frameName) + "." + Conversion.getFrameNumber(frameName); tester = new File(fullPath); if (tester.exists()) { return fullPath; } return null; } public static boolean canAccessFrame(String frameName) { Frame current = DisplayController.getCurrentFrame(); // Just in case the current frame is not yet saved... if (frameName.equals(current.getName())) { FrameIO.SaveFrame(current, false, false); current.change(); return true; } for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) { if (getFrameFullPathName(path, frameName) != null) { return true; } } return false; } public static Collection searchFrame(String frameName, String pattern, String path) { String fullPath = null; if (path == null) { for (String possiblePath : FolderSettings.FrameDirs.getAbsoluteDirs()) { fullPath = getFrameFullPathName(possiblePath, frameName); if (fullPath != null) { break; } } } else { fullPath = getFrameFullPathName(path, frameName); } // If the frame was not located return null if (fullPath == null) { return null; } Collection results = new LinkedList(); // Open the file and search the text items try { BufferedReader reader = new BufferedReader(new FileReader(fullPath)); String next; while (reader.ready() && ((next = reader.readLine()) != null)) { if (next.startsWith("T")) { String toSearch = next.substring(2); if (toSearch.toLowerCase().contains(pattern)) { results.add(toSearch); } } else if (next.startsWith("+T+")) { String toSearch = next.substring(4); if (toSearch.toLowerCase().contains(pattern)) { results.add(toSearch); } } } reader.close(); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } catch (IOException e) { e.printStackTrace(); } return results; } public static Frame LoadKnownPath(String path, String frameName) { String fullPath = getFrameFullPathName(path, frameName); if (fullPath == null) { return null; } try { FrameReader reader; // Get the frameset name. int i = frameName.length() - 1; for (; i >= 0; i--) { if (!Character.isDigit(frameName.charAt(i))) { break; } } if (i < 0) { System.err.println("LoadKnownFrame was provided with a invalid Frame name: " + frameName); return null; } String framesetName = frameName.substring(0, i + 1); String redirectTo = ExpReader.redirectTo(fullPath); while (redirectTo != null) { fullPath = path + framesetName + File.separator + redirectTo; redirectTo = ExpReader.redirectTo(fullPath); } if (fullPath.endsWith(ExpReader.EXTENTION)) { if (EncryptedExpReader.isEncryptedExpediteeFile(fullPath)) { if (EncryptedExpReader.isAccessibleExpediteeFile(fullPath)) { reader = new EncryptedExpReader(frameName); } else { String message = "Cannot load frame " + frameName + ". It is encrypted and you do not have the associated key."; System.err.println(message); MessageBay.errorMessage(message); return null; } } else { reader = new ExpReader(frameName); } } else { reader = new KMSReader(); } Frame frame = reader.readFrame(fullPath); if (frame == null) { MessageBay.errorMessage("Error: " + frameName + " could not be successfully loaded."); return null; } frame.setPath(path); // do not put 0 frames or virtual frames into the cache // Why are zero frames not put in the cache if (_Cache.size() > MAX_CACHE) { _Cache.clear(); } if (frame.getNumber() > 0 && isCacheOn()) { _Cache.put(frameName.toLowerCase(), frame); } return frame; } catch (IOException ioe) { ioe.printStackTrace(); Logger.Log(ioe); } catch (Exception e) { e.printStackTrace(); Logger.Log(e); MessageBay.errorMessage("Error: " + frameName + " could not be successfully loaded."); } return null; } public static void Reload() { // disable cache boolean cache = _UseCache; _UseCache = false; Frame fresh = FrameIO.LoadFrame(DisplayController.getCurrentFrame().getName()); _UseCache = cache; if (_Cache.containsKey(fresh.getName().toLowerCase())) { addToCache(fresh); } DisplayController.setCurrentFrame(fresh, false); } public static Frame LoadPrevious(Frame current) { checkTDFC(current); // the current name and number String name = current.getFramesetName(); int num = current.getNumber() - 1; // loop until a frame that exists is found for (; num >= 0; num--) { Frame f = LoadFrame(name + num, current.getPath()); if (f != null) { return f; } } // if we did not find another Frame then this one must be the last one // in the frameset MessageBay .displayMessageOnce("This is the first frame in the frameset"); return null; } /** * Returns the next Frame in the current Frameset (The Frame with the next * highest Frame number) If the current Frame is the last one in the * Frameset, or an error occurs then null is returned. * * @return The Frame after this one in the current frameset, or null */ public static Frame LoadNext(Frame current) { checkTDFC(current); // the current name and number int num = current.getNumber() + 1; int max = num + 1; String name = current.getFramesetName(); // read the maximum from the INF file try { max = ReadINF(current.getPath(), current.getFramesetName(), false); } catch (IOException ioe) { MessageBay.errorMessage("Error loading INF file for frameset '" + name + "'"); return null; } // loop until a frame that exists is found for (; num <= max; num++) { Frame f = LoadFrame(name + num, current.getPath()); if (f != null) { return f; } } // if we did not find another Frame then this one must be the last one // in the frameset MessageBay.displayMessageOnce("This is the last frame in the frameset"); return null; } /** * This method checks if the current frame has just been created with TDFC. * If it has the frame is saved regardless of whether it has been edited or * not and the TDFC item property is cleared. This is to ensure that the * link is saved on the parent frame. * * @param current */ public static void checkTDFC(Frame current) { if (FrameUtils.getTdfcItem() != null) { FrameUtils.setTdfcItem(null); current.change(); } } public static Frame LoadLast(String framesetName, String path) { // read the maximum from the INF file int max; try { max = ReadINF(path, framesetName, false); } catch (IOException ioe) { MessageBay.errorMessage("Error loading INF file for frameset '" + framesetName + "'"); return null; } // loop backwards until a frame that exists is found for (int num = max; num > 0; num--) { Frame f = LoadFromDisk(framesetName + num, path, false); if (f != null) { return f; } } // if we did not find another Frame then this one must be the last one // in the frameset MessageBay.displayMessage("This is the last frame in the frameset"); return null; } public static Frame LoadZero(String framesetName, String path) { return LoadFrame(framesetName + 0); } public static Frame LoadZero() { Frame current = DisplayController.getCurrentFrame(); return LoadZero(current.getFramesetName(), current.getPath()); } public static Frame LoadLast() { Frame current = DisplayController.getCurrentFrame(); return LoadLast(current.getFramesetName(), current.getPath()); } public static Frame LoadNext() { return LoadNext(DisplayController.getCurrentFrame()); } public static Frame LoadPrevious() { return LoadPrevious(DisplayController.getCurrentFrame()); } /** * Deletes the given Frame on disk and removes the cached Frame if there is * one. Also adds the deleted frame into the deletedFrames frameset. * * @param toDelete * The Frame to be deleted * @return The name the deleted frame was changed to, or null if the delete * failed */ public static String DeleteFrame(Frame toDelete) throws IOException, SecurityException { if (toDelete == null) { return null; } // Dont delete the zero frame if (toDelete.getNumber() == 0) { throw new SecurityException("Deleting a zero frame is illegal"); } // Dont delete the zero frame if (!toDelete.isLocal()) { throw new SecurityException("Attempted to delete remote frame"); } SaveFrame(toDelete); // Copy deleted frames to the DeletedFrames frameset // get the last used frame in the destination frameset final String DELETED_FRAMES = "DeletedFrames"; int lastNumber = FrameIO.getLastNumber(DELETED_FRAMES); String framePath; try { // create the new frameset Frame one = FrameIO.CreateFrameset(DELETED_FRAMES, toDelete .getPath(), null); framePath = one.getPath(); lastNumber = 0; } catch (Exception e) { Frame zero = FrameIO.LoadFrame(DELETED_FRAMES + "0"); framePath = zero.getPath(); } // get the fill path to determine which file version it is String source = getFrameFullPathName(toDelete.getPath(), toDelete .getName()); String oldFrameName = toDelete.getName().toLowerCase(); // Now save the frame in the new location toDelete.setFrameset(DELETED_FRAMES); toDelete.setFrameNumber(lastNumber + 1); toDelete.setPath(framePath); ForceSaveFrame(toDelete); if (_Cache.containsKey(oldFrameName)) { _Cache.remove(oldFrameName); } File del = new File(source); java.io.FileInputStream ff = new java.io.FileInputStream(del); ff.close(); if (del.delete()) { return toDelete.getName(); } return null; } /** * Creates a new Frame in the given frameset and assigns it the given Title, * which can be null. The newly created Frame is a copy of the frameset's .0 * file with the number updated based on the last recorded Frame name in the * frameset's INF file. * * @param frameset * The frameset to create the new Frame in * @param frameTitle * The title to assign to the newly created Frame (can be NULL). * @return The newly created Frame. */ public static synchronized Frame CreateFrame(String frameset, String frameTitle, String templateFrame) throws RuntimeException { if (!FrameIO.isValidFramesetName(frameset)) { throw new RuntimeException(frameset + " is not a valid frameset name"); } int next = -1; // disable caching of 0 frames // Mike says: Why is caching of 0 frames being disabled? /* * Especially since 0 frames are not event put into the cache in the * frist place */ // SuspendCache(); /* * Suspending the cache causes infinate loops when trying to load a zero * frame which has a ao which contains an v or av which contains a link * to the ao frame */ String zeroFrameName = frameset + "0"; Frame destFramesetZero = LoadFrame(zeroFrameName); if (destFramesetZero == null) { throw new RuntimeException(zeroFrameName + " could not be found"); } Frame template = null; if (templateFrame == null) { // load in frame.0 template = destFramesetZero; } else { template = LoadFrame(templateFrame); if (template == null) { throw new RuntimeException("LinkTemplate " + templateFrame + " could not be found"); } } ResumeCache(); // read the next number from the INF file try { next = ReadINF(destFramesetZero.getPath(), frameset, true); } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException("INF file could not be read"); } // Remove the old frame from the cache then add the new one // TODO figure out some way that we can put both in the cache _Cache.remove(template.getName().toLowerCase()); // set the number and title of the new frame template.setName(frameset, ++next); template.setTitle(frameTitle); // _Cache.put(template.getName().toLowerCase(), template); Logger.Log(Logger.SYSTEM, Logger.TDFC, "Creating new frame: " + template.getName() + " from TDFC"); template.setOwner(UserSettings.UserName.get()); template.reset(); template.resetDateCreated(); for (Item i : template.getSortedItems()) { if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT)) { i.setLink(null); } } // do auto shrinking of the title IF not in twin frames mode and the title is not centred Item titleItem = template.getTitleItem(); if (titleItem == null) { return template; } boolean titleItemJustified = titleItem == null || !Justification.center.equals(((Text)titleItem).getJustification()); if (!DisplayController.isTwinFramesOn() && titleItemJustified) { if ((titleItem.getX() + 1) < template.getNameItem().getX()) { int title_item_xr = titleItem.getX() + titleItem.getBoundsWidth(); // should really be '... -1' int frame_name_xl = template.getNameItem().getX(); if (frame_name_xl < DisplayController.MINIMUM_FRAME_WIDTH) { frame_name_xl = DisplayController.MINIMUM_FRAME_WIDTH; } while ((titleItem.getSize() > Text.MINIMUM_FONT_SIZE) && title_item_xr > frame_name_xl) { titleItem.setSize(titleItem.getSize() - 1); System.err.println("**** shrunk titleItem: " + titleItem + " to font size: " + titleItem.getSize()); } } else { System.out.println("Bad title x position: " + titleItem.getX()); } } // Assign a width to the title. titleItem.setRightMargin(template.getNameItem().getX(), true); return template; } public static void DisableCache() { //System.err.println(" --------- Cache Disabled --------- "); _UseCache = false; } public static void EnableCache() { //System.err.println(" --------- Cache Enabled --------- "); _UseCache = true; } public static void SuspendCache() { //System.err.println("SuspendCache: _UseCache" + " was " + _UseCache); if (_UseCache) { DisableCache(); _SuspendedCache = true; } else { _SuspendedCache = false; } //System.err.println(" Cache is suspended -> " + _SuspendedCache); //System.err.println(" _UseCache is -> " + _UseCache); //System.err.println(); } public static void ResumeCache() { //System.err.println("ResumeCache: _UseCache" + " was " + _UseCache); if (_SuspendedCache) { EnableCache(); _SuspendedCache = false; } //System.err.println(" Cache is suspended -> " + _SuspendedCache); //System.err.println(" _UseCache is -> " + _UseCache); //System.err.println(); } public static void RefreshCacheImages() { SuspendCache(); for (Frame frame : _Cache.values()) { frame.setBuffer(null); } ResumeCache(); } /** * Creates a new frameset using the given name. This includes creating a new * subdirectory in the FRAME_PATH directory, Copying over the * default.0 frame from the default frameset, copying the .0 Frame to make a * .1 Frame, and creating the frameset's INF file. * * @param frameset * The name of the Frameset to create * @param zeroFrameItems TODO * @return The first Frame of the new Frameset (Frame.1) */ public static Frame CreateFrameset(String frameset, String path, Collection zeroFrameItems) throws Exception { return CreateFrameset(frameset, path, false, zeroFrameItems); } /** * Tests if the given String is a 'proper' framename, that is, the String * must begin with a character, end with a number with 0 or more letters and * numbers in between. * * @param frameName * The String to test for validity as a frame name * @return True if the given framename is proper, false otherwise. */ public static boolean isValidFrameName(String frameName) { if (frameName == null || frameName.length() < 2) { return false; } int lastCharIndex = frameName.length() - 1; // String must begin with a letter and end with a digit if (!Character.isLetter(frameName.charAt(0)) || !Character.isDigit(frameName.charAt(lastCharIndex))) { return false; } // All the characters between first and last must be letters // or digits for (int i = 1; i < lastCharIndex; i++) { if (!isValidFrameNameChar(frameName.charAt(i))) { return false; } } return true; } private static boolean isValidFrameNameChar(char c) { return c == '-' || c == '.' || Character.isLetterOrDigit(c); } /** * Saves the given Frame to disk in the corresponding frameset directory. * This is the same as calling SaveFrame(toSave, true) * * @param toSave * The Frame to save to disk */ public static String SaveFrame(Frame toSave) { return SaveFrame(toSave, true); } /** * Saves a frame. * * @param toSave * the frame to save * @param inc * true if the frames counter should be incremented * @return the text content of the frame */ public static String SaveFrame(Frame toSave, boolean inc) { return SaveFrame(toSave, inc, true); } /** * Saves the given Frame to disk in the corresponding frameset directory, if * inc is true then the saved frames counter is incremented, otherwise it is * untouched. * * @param toSave * The Frame to save to disk * @param inc * True if the saved frames counter should be incremented, false otherwise. * @param checkBackup * True if the frame should be checked for the backup tag */ public static String SaveFrame(Frame toSave, boolean inc, boolean checkBackup) { // TODO When loading a frame maybe append onto the event history too- // with a break to indicate the end of a session if (toSave == null || !toSave.hasChanged() || toSave.isSaved()) { return ""; } // Don't save if the frame is protected and it exists if (checkBackup && toSave.isReadOnly()) { _Cache.remove(toSave.getName().toLowerCase()); return ""; } /* Don't save the frame if it has the noSave tag */ if (toSave.hasAnnotation("nosave")) { Actions.LegacyPerformActionCatchErrors(toSave, null, "Restore"); return ""; } // Save frame that is not local through the Networking classes if (!toSave.isLocal()) { return FrameShare.getInstance().saveFrame(toSave); } /* Format the frame if it has the autoFormat tag */ if (toSave.hasAnnotation("autoformat")) { Actions.LegacyPerformActionCatchErrors(toSave, null, "Format"); } /** * Get the full path only to determine which format to use for saving * the frame. At this stage use Exp format for saving Exp frames only. * Later this will be changed so that KMS frames will be updated to the * Exp format. */ String fullPath = getFrameFullPathName(toSave.getPath(), toSave .getName()); // Check if the frame exists if (checkBackup && fullPath == null) { // The first time a frame with the backup tag is saved, don't back it up checkBackup = false; } FrameWriter writer = null; int savedVersion; try { // if its a new frame or an existing Exp frame... if (fullPath == null || fullPath.endsWith(ExpReader.EXTENTION)) { if (toSave.isEncryptableFrame()) { String encryptionLabel = toSave.getFrameEncryptionLabel(); writer = new EncryptedExpWriter(encryptionLabel); } else { writer = new ExpWriter(); } savedVersion = ExpReader.getVersion(fullPath); // Is the file this would be saved to a redirect? String redirectTo = ExpReader.redirectTo(fullPath); if (redirectTo != null) { String redirectedPath = toSave.getFramePathReal(); writer.setOutputLocation(redirectedPath); } } else { writer = new KMSWriter(); savedVersion = KMSReader.getVersion(fullPath); } // Check if the frame doesn't exist // if (savedVersion < 0) { // /* // * This will happen if the user has two Expeditee's running at // * once and closes the first. When the second one closes the // * messages directory will have been deleted. // */ // MessageBay // .errorMessage("Could not save frame that does not exist: " // + toSave.getName()); // return null; // } // Check if we are trying to save an out of date version // Q: Why do we ignore version conflicts if the saved version is zero? // A: Sometimes a Frame object in memory with a specified path is not 'connected' // to the file found at that specified path yet. This occurs if a frame object // has been created, its path assigned and saved to disk; with the intention // discarding this Frame object and later saving a different Frame object to // that File. One example of this is when @old frames are created. // The new Frame object that is created and saved only to be discarded, has a // version number of zero. // Therefore, if the file created from the discarded Frame has its modification // date compared to the modification date on the Frame object that will eventually // be used to overwrite that file, it causes a false positive conflict. Checking // for the zero version number fixes this. //String framesetName = toSave.getFramesetName(); boolean isBayFrameset = toSave.isBayFrameset(); long fileLastModify = fullPath != null ? new File(fullPath).lastModified() : 0; long frameLastModify = toSave.getLastModifyPrecise(); boolean fileModifyConflict = fileLastModify > frameLastModify && !isBayFrameset; boolean versionConflict = savedVersion > toSave.getVersion() && !isBayFrameset; if ((fileModifyConflict || versionConflict) && savedVersion > 0) { // remove this frame from the cache if it is there // This will make sure links to the original are set correctly _Cache.remove(toSave.getName().toLowerCase()); int nextnum = ReadINF(toSave.getPath(), toSave.getFramesetName(), false) + 1; SuspendCache(); Frame original = LoadFrame(toSave.getName()); toSave.setFrameNumber(nextnum); ResumeCache(); // Put the modified version in the cache addToCache(toSave); // Show the messages alerting the user Text originalMessage = new Text(-1); originalMessage.setColor(MessageBay.ERROR_COLOR); StringBuilder message = new StringBuilder(original.getName() + " was updated by another user. "); if (fileModifyConflict) { message.append("{ File modify conflict }"); System.err.println("Thread name: " + Thread.currentThread().getName()); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement ste: stackTrace) { System.err.println(ste.toString()); } } if (versionConflict) { message.append("{ Version conflict }"); } originalMessage.setText(message.toString()); originalMessage.setLink(original.getName()); Text yourMessage = new Text(-1); yourMessage.setColor(MessageBay.ERROR_COLOR); yourMessage.setText("Your version was renamed " + toSave.getName()); yourMessage.setLink(toSave.getName()); MessageBay.displayMessage(originalMessage); MessageBay.displayMessage(yourMessage); EcosystemManager.getMiscManager().beep(); } else if (checkBackup && ItemUtils.ContainsExactTag(toSave.getSortedItems(), ItemUtils.TAG_BACKUP)) { SuspendCache(); String oldFramesetName = toSave.getFramesetName() + "-old"; Frame original = LoadFrame(toSave.getName()); if (original == null) { original = toSave; } int orignum = original.getNumber(); int nextnum = -1; try { nextnum = ReadINF(toSave.getPath(), oldFramesetName, false) + 1; } catch (RuntimeException e) { try { CreateFrameset(oldFramesetName, toSave.getPath(), null); nextnum = 1; } catch (Exception e1) { e1.printStackTrace(); } } if (nextnum > 0) { original.setFrameset(oldFramesetName); original.setFrameNumber(nextnum); original.setPermission(new PermissionTriple(UserAppliedPermission.copy)); original.change(); SaveFrame(original, false, false); } Item i = ItemUtils.FindExactTag(toSave.getSortedItems(), ItemUtils.TAG_BACKUP); i.setLink(original.getName()); toSave.setFrameNumber(orignum); ResumeCache(); } // int oldMode = FrameGraphics.getMode(); // if (oldMode != FrameGraphics.MODE_XRAY) // FrameGraphics.setMode(FrameGraphics.MODE_XRAY, true); writer.writeFrame(toSave); // FrameGraphics.setMode(oldMode, true); toSave.setSaved(); // Update general stuff about frame setSavedProperties(toSave); if (inc) { SessionStats.SavedFrame(toSave.getName()); } // avoid out-of-sync frames (when in TwinFrames mode) if (_Cache.containsKey(toSave.getName().toLowerCase())) { addToCache(toSave); } Logger.Log(Logger.SYSTEM, Logger.SAVE, "Saving " + toSave.getName() + " to disk."); // check that the INF file is not out of date int last = ReadINF(toSave.getPath(), toSave.getFramesetName(), false); if (last <= toSave.getNumber()) { WriteINF(toSave.getPath(), toSave.getFramesetName(), toSave .getName()); } // check if this was the profile frame (and thus needs // re-parsing) if (isProfileFrame(toSave)) { Frame profile = FrameIO.LoadFrame(toSave.getFramesetName() + "1"); assert (profile != null); FrameUtils.ParseProfile(profile); } } catch (IOException ioe) { ioe.printStackTrace(); ioe.getStackTrace(); Logger.Log(ioe); return null; } toSave.notifyObservers(false); return writer.getFileContents(); } /** * Saves the given Frame to disk in the corresponding frameset directory as a RESTORE, if * inc is true then the saved frames counter is incremented, otherwise it is * untouched. * * @param toSave * The Frame to save to disk as the DEFAULT COPY * @param inc * True if the saved frames counter should be incremented, false * otherwise. * @param checkBackup * True if the frame should be checked for the back up tag */ public static String SaveFrameAsRestore(Frame toSave, boolean inc, boolean checkBackup) { String sf = SaveFrame(toSave, inc, checkBackup); String fullPath = getFrameFullPathName(toSave.getPath(), toSave .getName()); //System.out.println(fullPath); String restoreVersion = fullPath + ".restore"; File source = new File(fullPath); File dest = new File(restoreVersion); FileChannel inputChannel = null; FileChannel outputChannel = null; try{ FileInputStream source_fis = new FileInputStream(source); inputChannel = source_fis.getChannel(); FileOutputStream dest_fos = new FileOutputStream(dest); outputChannel = dest_fos.getChannel(); outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); inputChannel.close(); outputChannel.close(); source_fis.close(); dest_fos.close(); } catch(Exception e){ e.printStackTrace(); } return sf; } /** * @param toAdd */ public static void addToCache(Frame toAdd) { _Cache.put(toAdd.getName().toLowerCase(), toAdd); } public static void ClearCache() { _Cache.clear(); } /** * Checks if a frame is in the current user profile frameset. * * @param toCheck * the frame to check * @return true if the frame is in the current user profile frameset */ public static boolean isProfileFrame(Frame toCheck) { if (toCheck.getNumber() == 0 || toCheck.getFramesetName().equals(UserSettings.DEFAULT_PROFILE_NAME)) { return false; } return toCheck.getPath().equals(PROFILE_PATH); // return toCheck.getFramesetName() // .equalsIgnoreCase(UserSettings.ProfileName); } public static Frame LoadProfile(String userName) { final String profilesLoc = System.getProperty("profiles.loc"); if (profilesLoc != null) { return LoadFrame(userName + "1", profilesLoc); } else { return LoadFrame(userName + "1"); } } public static Frame CreateNewProfile(String username, Map initialSettings, Map> toNotifyOnSet) throws InvalidFramesetNameException, ExistingFramesetException { // Frame profile = CreateFrameset(username, PROFILE_PATH, true); // if (profile != null) { // FrameUtils.CreateDefaultProfile(username, profile, initialSettings, toNotifyOnSet); // } else { // System.err.println("An error occured while attempting to create the profile named: " + username); // System.err.println("Unable to proceed."); // System.exit(1); // } // return profile; if (username.equals(UserSettings.DEFAULT_PROFILE_NAME)) { ProfileManager.ensureDefaultProfile(); return FrameIO.LoadFrame(UserSettings.DEFAULT_PROFILE_NAME + "1"); } else { return ProfileManager.createProfile(username, initialSettings, toNotifyOnSet); } } /** * Reads the INF file that corresponds to the given Frame name * * @param framename * The Frame to lookup the INF file for * @throws IOException * Any exceptions encountered by the BufferedReader used to read * the INF. */ public static int ReadINF(String path, String frameset, boolean update) throws IOException { assert (!frameset.endsWith(".")); try { // read INF BufferedReader reader; try { // Check on the local drive reader = new BufferedReader(new FileReader(path + frameset.toLowerCase() + File.separator + INF_FILENAME)); } catch (Exception e) { reader = new BufferedReader(new FileReader(path + frameset.toLowerCase() + File.separator + frameset.toLowerCase() + ".inf")); } String inf = reader.readLine(); reader.close(); int next = Conversion.getFrameNumber(inf); // update INF file if (update) { try { WriteINF(path, frameset, frameset + (next + 1)); } catch (IOException ioe) { ioe.printStackTrace(); Logger.Log(ioe); } } return next; } catch (Exception e) { } // Check peers return FrameShare.getInstance().getInfNumber(path, frameset, update); } /** * Writes the given String out to the INF file corresponding to the current * frameset. * * @param toWrite * The String to write to the file. * @throws IOException * Any exception encountered by the BufferedWriter. */ public static void WriteINF(String path, String frameset, String frameName) throws IOException { try { assert (!frameset.endsWith(".")); path += frameset.toLowerCase() + File.separator + INF_FILENAME; BufferedWriter writer = new BufferedWriter(new FileWriter(path)); writer.write(frameName); writer.close(); } catch (Exception e) { } } public static boolean FrameIsCached(String name) { return _Cache.containsKey(name); } /** * Gets a frame from the cache. * * @param name * The frame to get from the cache * * @return The frame from cache. Null if not cached. */ public static Frame FrameFromCache(String name) { return _Cache.get(name); } public static String ConvertToValidFramesetName(String toValidate) { assert (toValidate != null && toValidate.length() > 0); StringBuffer result = new StringBuffer(); if (Character.isDigit(toValidate.charAt(0))) { result.append(FRAME_NAME_LAST_CHAR); } boolean capital = false; for (int i = 0; i < toValidate.length() && result.length() < MAX_NAME_LENGTH; i++) { char cur = toValidate.charAt(i); // capitalize all characters after spaces if (Character.isLetterOrDigit(cur)) { if (capital) { capital = false; result.append(Character.toUpperCase(cur)); } else { result.append(cur); } } else { capital = true; } } assert (result.length() > 0); int lastCharIndex = result.length() - 1; if (!Character.isLetter(result.charAt(lastCharIndex))) { if (lastCharIndex == MAX_NAME_LENGTH - 1) { result.setCharAt(lastCharIndex, FRAME_NAME_LAST_CHAR); } else { result.append(FRAME_NAME_LAST_CHAR); } } assert (isValidFramesetName(result.toString())); return result.toString(); } public static Frame CreateNewFrame(Item linker) throws RuntimeException { String title = linker.getName(); String templateLink = linker.getAbsoluteLinkTemplate(); String framesetLink = linker.getAbsoluteLinkFrameset(); String frameset = (framesetLink != null ? framesetLink : DisplayController .getCurrentFrame().getFramesetName()); Frame newFrame = FrameIO.CreateFrame(frameset, title, templateLink); return newFrame; } public static Frame CreateNewFrame(Item linker, OnNewFrameAction action) throws RuntimeException { Frame newFrame = FrameIO.CreateNewFrame(linker); if(action != null) { action.exec(linker, newFrame); } return newFrame; } /** * Creates a new Frameset on disk, including a .0, .1, and .inf files. The * Default.0 frame is copied to make the initial .0 and .1 Frames * * @param name * The Frameset name to use * @param zeroFrameItems TODO * @return The name of the first Frame in the newly created Frameset (the .1 * frame) */ public static Frame CreateNewFrameset(String name, Collection zeroFrameItems) throws Exception { String path = DisplayController.getCurrentFrame().getPath(); // if current frameset is profile directory change it to framesets if (path.equals(FrameIO.PROFILE_PATH)) { path = FrameIO.FRAME_PATH; } Frame newFrame = FrameIO.CreateFrameset(name, path, zeroFrameItems); if (newFrame == null) { // Cant create directories if the path is readonly or there is no // space available newFrame = FrameIO.CreateFrameset(name, FrameIO.FRAME_PATH, zeroFrameItems); } if (newFrame == null) { // TODO handle running out of disk space here } return newFrame; } public static Frame CreateNewGroup(String name, Collection zeroFrameItems) { try { Frame oneFrame = FrameIO.CreateFrameset(name, FrameIO.GROUP_PATH, zeroFrameItems); oneFrame.setPermission(new PermissionTriple(UserAppliedPermission.full, UserAppliedPermission.none, UserAppliedPermission.none)); Text ownerAnnotation = oneFrame.createNewText("@Owner: " + UserSettings.UserName.get()); ownerAnnotation.setPosition(100, 100); ownerAnnotation.setPermission(new PermissionTriple(UserAppliedPermission.full, UserAppliedPermission.none, UserAppliedPermission.none)); Text membersAnnotation = oneFrame.createNewText("@Members: "); membersAnnotation.setPosition(100, 200); FrameIO.SaveFrame(oneFrame); FrameIO.LoadFrame(name + 0, FrameIO.GROUP_PATH).setPermission(new PermissionTriple(UserAppliedPermission.full, UserAppliedPermission.none, UserAppliedPermission.none)); return oneFrame; } catch (Exception e) { MessageBay.displayMessage("Unable to create group with name: " + name + ". See console for more details."); e.printStackTrace(); return null; } } /** * * @param frameset * @return */ public static int getLastNumber(String frameset) { // Rob thinks it might // have been // GetHighestNumExFrame // TODO minimise the number of frames being read in!! int num = -1; Frame zero = LoadFrame(frameset + "0"); // the frameset does not exist (or has no 0 frame) if (zero == null) { return -1; } try { num = ReadINF(zero.getPath(), frameset, false); } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); } /* * Michael doesnt think the code below is really needed... it will just * slow things down when we are reading frames over a network***** for (; * num >= 0; num--) { System.out.println("This code is loading frames to * find the highest existing frame..."); if (LoadFrame(frameset + num) != * null) break; } */ return num; } /** * Checks if a given frameset is accessable. * * @param framesetName * @return */ public static boolean canAccessFrameset(String framesetName) { framesetName = framesetName.toLowerCase(); for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) { if (canAccessFrameset(framesetName, Paths.get(path))) { return true; } } return false; } public static boolean canAccessFrameset(String framesetName, Path path) { File framesetDir = path.resolve(framesetName).toFile(); if (framesetDir.exists() && framesetDir.isDirectory()) { return true; } else { return false; } } public static Frame CreateFrameset(String frameset, String path, boolean recreate, Collection zeroFrameItems) throws InvalidFramesetNameException, ExistingFramesetException { String conversion = frameset + " --> "; if (!isValidFramesetName(frameset)) { throw new InvalidFramesetNameException(frameset); } if (!recreate && FrameIO.canAccessFrameset(frameset)) { throw new ExistingFramesetException(frameset); } conversion += frameset; Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Frameset Name: " + conversion); conversion = frameset; /** * TODO: Update this to exclude any\all invalid filename characters */ // ignore annotation character if (frameset.startsWith("@")) { frameset = frameset.substring(1); } conversion += " --> " + frameset; Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Name: " + conversion); // create the new Frameset directory File dir = new File(path + frameset.toLowerCase() + File.separator); // If the directory doesnt already exist then create it... if (!dir.exists()) { if (!dir.mkdirs()) { /* * If the directory does not exist, but could not be created then there is something wrong. * Prior to May 2019 the only known reason for this was because the disk could be full. * Since then, we have discovered that null can occur when working with Google file stream. * A directory can return false to an existence check, but then fail to create the directory * due to it already existing because of sync issues. While we have not confirmed, this may * be the case with other network drives as well. */ System.err.println("Failed to create directory for frameset: " + frameset); return null; } } // create the new INF file try { WriteINF(path, frameset, frameset + '1'); } catch (IOException ioe) { ioe.printStackTrace(); Logger.Log(ioe); } SuspendCache(); // copy the default .0 and .1 files Frame base = null; try { base = LoadFrame(TemplateSettings.DefaultFrame.get()); } catch (Exception e) { } // The frame may not be accessed for various reasons... in all these // cases just create a new one if (base == null) { base = new Frame(); } ResumeCache(); // 0 frame base.reset(); base.resetDateCreated(); base.setFrameset(frameset); base.setFrameNumber(0); base.setOwner(UserSettings.UserName.get()); base.setTitle(base.getFramesetName() + "0"); base.setPath(path); base.change(); if (zeroFrameItems != null) { base.addAllItems(zeroFrameItems); } SaveFrame(base, false); // 1 frame base.reset(); base.resetDateCreated(); base.setFrameNumber(1); base.setOwner(UserSettings.UserName.get()); base.setTitle(frameset); base.change(); SaveFrame(base, true); FrameIO.setSavedProperties(base); Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Created new frameset: " + frameset); return base; } /** * Tests if a frameset name is valid. That is it must begin and end with a * letter and contain only letters and digits in between. * * @param frameset * the name to be tested * @return true if the frameset name is valid */ public static boolean isValidFramesetName(String frameset) { if (frameset == null) { return false; } int nameLength = frameset.length(); if (frameset.length() <= 0 || nameLength > MAX_NAME_LENGTH) { return false; } int lastCharIndex = nameLength - 1; if (!Character.isLetter(frameset.charAt(0)) || !Character.isLetter(frameset.charAt(lastCharIndex))) { return false; } for (int i = 1; i < lastCharIndex; i++) { if (!isValidFrameNameChar(frameset.charAt(i))) { return false; } } return true; } public static boolean deleteFrameset(String framesetName) { return moveFrameset(framesetName, FrameIO.TRASH_PATH, true); } public static boolean moveFrameset(String framesetName, String destinationFolder, boolean override) { if (!FrameIO.canAccessFrameset(framesetName)) { return false; } // Clear the cache _Cache.clear(); // Search all the available directories for the directory for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) { return moveFrameset(framesetName, path, destinationFolder, override); } return false; } public static boolean moveFrameset(String framesetName, String path, String destinationFolder, boolean override) { String source = path + framesetName.toLowerCase() + File.separator; File framesetDirectory = new File(source); // Once we have found the directory move it if (framesetDirectory.exists()) { String destPath = destinationFolder + framesetName.toLowerCase(); int copyNumber = 1; File dest = new File(destPath + File.separator); // Create the destination folder if it doesnt already exist if (!dest.getParentFile().exists()) { dest.mkdirs(); } // If a frameset with the same name is already in the // destination add // a number to the end while (dest.exists() && !override) { dest = new File(destPath + ++copyNumber + File.separator); } try { moveFileTree(framesetDirectory.toPath(), dest.toPath()); } catch (IOException e) { e.printStackTrace(); return false; } for (File f : framesetDirectory.listFiles()) { if (!f.delete()) { return false; } } if (!framesetDirectory.delete()) { return false; } return true; } else { return false; } } public static boolean CopyFrameset(String framesetToCopy, String copiedFrameset) throws Exception { if (!FrameIO.canAccessFrameset(framesetToCopy)) { return false; } if (FrameIO.canAccessFrameset(copiedFrameset)) { return false; } // search through all the directories to find the frameset we are // copying for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) { String source = path + framesetToCopy.toLowerCase() + File.separator; File framesetDirectory = new File(source); if (framesetDirectory.exists()) { // copy the frameset File copyFramesetDirectory = new File(path + copiedFrameset.toLowerCase() + File.separator); if (!copyFramesetDirectory.mkdirs()) { return false; } // copy each of the frames for (File f : framesetDirectory.listFiles()) { // Ignore hidden files if (f.getName().charAt(0) == '.') { continue; } String copyPath = copyFramesetDirectory.getAbsolutePath() + File.separator + f.getName(); FrameIO.copyFile(f.getAbsolutePath(), copyPath); } return true; } } return false; } /** * Copies a file from one location to another. * * @param existingFile * @param newFileName * @throws Exception */ public static void copyFile(String existingFile, String newFileName) throws IOException { FileInputStream is = new FileInputStream(existingFile); FileOutputStream os = new FileOutputStream(newFileName, false); int data; while ((data = is.read()) != -1) { os.write(data); } os.flush(); os.close(); is.close(); } /** * Saves a frame regardless of whether or not the frame is marked as having * been changed. * * @param frame * the frame to save * @return the contents of the frame or null if it could not be saved */ public static String ForceSaveFrame(Frame frame) { frame.change(); return SaveFrame(frame, false); } public static boolean isValidLink(String frameName) { return frameName == null || isPositiveInteger(frameName) || isValidFrameName(frameName); } public static void SavePublicFrame(String peerName, String frameName, int version, BufferedReader packetContents) { // TODO handle versioning - add version to the header // Remote user uploads version based on an old version // Remove it from the cache so that next time it is loaded we get the up // todate version _Cache.remove(frameName.toLowerCase()); // Save to file String filename = PUBLIC_PATH + Conversion.getFramesetName(frameName) + File.separator + Conversion.getFrameNumber(frameName) + ExpReader.EXTENTION; File file = new File(filename); // Ensure the file exists if (file.exists()) { // Check the versions int savedVersion = ExpReader.getVersion(filename); if (savedVersion > version) { // remove this frame from the cache if it is there // This will make sure links to the original are set correctly // _Cache.remove(frameName.toLowerCase()); int nextNum = 0; try { nextNum = ReadINF(PUBLIC_PATH, Conversion .getFramesetName(frameName), false) + 1; } catch (IOException e) { e.printStackTrace(); } String newName = Conversion.getFramesetName(frameName) + nextNum; filename = PUBLIC_PATH + Conversion.getFramesetName(frameName) + File.separator + nextNum + ExpReader.EXTENTION; // Show the messages alerting the user Text originalMessage = new Text(-1); originalMessage.setColor(MessageBay.ERROR_COLOR); originalMessage.setText(frameName + " was edited by " + peerName); originalMessage.setLink(frameName); Text yourMessage = new Text(-1); yourMessage.setColor(MessageBay.ERROR_COLOR); yourMessage.setText("Their version was renamed " + newName); yourMessage.setLink(newName); MessageBay.displayMessage(originalMessage); MessageBay.displayMessage(yourMessage); Frame editedFrame = FrameIO.LoadFrame(frameName); FrameShare.getInstance().sendMessage( frameName + " was recently edited by " + editedFrame.getLastModifyUser(), peerName); FrameShare.getInstance().sendMessage( "Your version was renamed " + newName, peerName); } } // Save the new version try { // FileWriter fw = new FileWriter(file); // Open an Output Stream Writer to set encoding OutputStream fout = new FileOutputStream(file); OutputStream bout = new BufferedOutputStream(fout); Writer fw = new OutputStreamWriter(bout, "UTF-8"); String nextLine = null; while ((nextLine = packetContents.readLine()) != null) { fw.write(nextLine + '\n'); } fw.flush(); fw.close(); MessageBay.displayMessage("Saved remote frame: " + frameName); } catch (IOException e) { MessageBay.errorMessage("Error remote saving " + frameName + ": " + e.getMessage()); e.printStackTrace(); } } public static void setSavedProperties(Frame toSave) { toSave.setLastModifyDate(Formatter.getDateTime(), System.currentTimeMillis()); toSave.setLastModifyUser(UserSettings.UserName.get()); toSave.setVersion(toSave.getVersion() + 1); Time darkTime = new Time(SessionStats.getFrameDarkTime().getTime() + toSave.getDarkTime().getTime()); Time activeTime = new Time(SessionStats.getFrameActiveTime().getTime() + toSave.getActiveTime().getTime()); toSave.setDarkTime(darkTime); toSave.setActiveTime(activeTime); } public static boolean personalResourcesExist(String username) { Path personalResources = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + username); File personalResourcesFile = personalResources.toFile(); boolean directoryExists = personalResourcesFile.exists() && personalResourcesFile.isDirectory(); return directoryExists; } public static Path setupPersonalResources(String username) { Path personalResources = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + username); personalResources.toFile().mkdir(); File[] globalResourcesToCopy = Paths.get(FrameIO.RESOURCES_PRIVATE_PATH).toFile().listFiles(); try { for (File toCopy: globalResourcesToCopy) { Path p = Paths.get(toCopy.getAbsolutePath()); if (!p.getFileName().toString().equals(".res") && !p.getFileName().toString().equals("about")) { moveFileTree(p.toAbsolutePath(), personalResources.resolve(p.getFileName())); } } } catch (IOException e) { e.printStackTrace(); personalResources = null; } return personalResources; } public static void migrateFrame(Frame toMigrate, Path destinationDirectory) { Path source = Paths.get(toMigrate.getFramePathReal()); String destination = source.relativize(destinationDirectory).toString().substring(3).replace(File.separator, "/"); try { Files.move(source, destinationDirectory); } catch (IOException e) { System.err.println("FrameIO::migrateFrame: failed to migrate from to new location. Message: " + e.getMessage()); return; } try { FileWriter out = new FileWriter(source.toFile()); out.write("REDIRECT:" + destination); out.flush(); out.close(); } catch (IOException e) { System.err.println("FrameIO::migrateFrame: failed to update file [" + source + "] to redirect to [" + destination + "] following migration. Message: " + e.getMessage()); } } private static void moveFileTree(Path source, Path target) throws IOException { if (source.toFile().isDirectory()) { if (!target.toFile().exists()) { Files.copy(source, target); } File[] files = source.toFile().listFiles(); for (File file: files) { Path asPath = Paths.get(file.getAbsolutePath()); moveFileTree(asPath, target.resolve(asPath.getFileName())); } } else { Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); } } }