/**
* 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);
}
}
}