/**
* 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.sql.Time;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import org.expeditee.actions.Actions;
import org.expeditee.agents.ExistingFramesetException;
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.PermissionPair;
import org.expeditee.items.Text;
import org.expeditee.items.UserAppliedPermission;
import org.expeditee.network.FrameShare;
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';
public static void changeParentFolder(String newFolder) {
PARENT_FOLDER = newFolder;
PUBLIC_PATH = PARENT_FOLDER + "public" + File.separator;
FRAME_PATH = PARENT_FOLDER + "framesets" + File.separator;
MESSAGES_PATH = PARENT_FOLDER + "messages" + File.separator;
TRASH_PATH = PARENT_FOLDER + "trash" + File.separator;
IMAGES_PATH = PARENT_FOLDER + IMAGES_FOLDER;
HELP_PATH = PARENT_FOLDER + "documentation" + File.separator;
DICT_PATH = PARENT_FOLDER + "dict" + File.separator;
FONT_PATH = PARENT_FOLDER + "fonts" + File.separator;
PROFILE_PATH = PARENT_FOLDER + "profiles" + File.separator;
EXPORTS_DIR = PARENT_FOLDER + "exports" + File.separator;
STATISTICS_DIR = PARENT_FOLDER + "statistics" + File.separator;
LOGS_DIR = PARENT_FOLDER + "logs" + File.separator;
}
/**
* The default location for storing the framesets. Each frameset has its own
* subdirectory in this directory.
*/
public static String IMAGES_FOLDER = "images" + File.separator;
public static String TRASH_PATH;
public static String PARENT_FOLDER;
public static String FRAME_PATH;
public static String MESSAGES_PATH;
public static String PUBLIC_PATH;
public static String IMAGES_PATH;
public static String HELP_PATH;
public static String FONT_PATH;
public static String TEMPLATES_PATH;
public static String DICT_PATH;
public static String PROFILE_PATH;
public static String EXPORTS_DIR;
public static String STATISTICS_DIR;
public static String LOGS_DIR;
private static final String INF_FILENAME = "frame.inf";
public static final String ILLEGAL_CHARS = ";:\\/?";
public static final int MAX_NAME_LENGTH = 64;
public static final int MAX_CACHE = 100;
private static HashMap _Cache = new FrameCache();
// private static HashMap _FramesetNameCache = new
// HashMap();
private static boolean ENABLE_CACHE = true;
private static boolean _UseCache = true;
private static boolean _SuspendedCache = false;
// 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;
}
public static Frame LoadFrame(String frameName) {
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);
return frame;
}
Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
+ " from disk.");
return LoadFromDisk(frameName, path, ignoreAnnotations);
}
//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.");
Frame frame = _Cache.get(frameNameLower);
_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 ignoreAnnotations) {
Frame loaded = null;
if (knownPath != null) {
loaded = LoadKnowPath(knownPath, framename);
} else {
for (String path : FolderSettings.FrameDirs.get()) {
loaded = LoadKnowPath(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);
}
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.get()) {
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 the full path and file name of the frame.
*
* @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.get()) {
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.get()) {
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);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
}
return results;
}
private static Frame LoadKnowPath(String path, String frameName) {
String fullPath = getFrameFullPathName(path, frameName);
if (fullPath == null)
return null;
try {
FrameReader reader;
if (fullPath.endsWith(ExpReader.EXTENTION)) {
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());
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.getItems()) {
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 (!DisplayController.isTwinFramesOn() && !Justification.center.equals(((Text)titleItem).getJustification())) {
if ((titleItem.getX() + 1) < template.getNameItem().getX()) {
while (titleItem.getSize() > Text.MINIMUM_FONT_SIZE
&& titleItem.getBoundsWidth() + titleItem.getX() > template
.getNameItem().getX()) {
titleItem.setSize(titleItem.getSize() - 1);
}
} 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() {
_UseCache = false;
}
public static void EnableCache() {
_UseCache = true;
}
public static void SuspendCache() {
if (_UseCache) {
DisableCache();
_SuspendedCache = true;
} else {
_SuspendedCache = false;
}
}
public static void ResumeCache() {
if (_SuspendedCache) {
EnableCache();
_SuspendedCache = false;
}
}
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
* @return The first Frame of the new Frameset (Frame.1)
*/
public static Frame CreateFrameset(String frameset, String path)
throws Exception {
return CreateFrameset(frameset, path, false);
}
/**
* 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 Character.isLetterOrDigit(c) || 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 back up 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 "";
}
// Dont save if the frame is protected and it exists
if (checkBackup && toSave.isReadOnly()) {
_Cache.remove(toSave.getName().toLowerCase());
return "";
}
/* Dont save the frame if it has the noSave tag */
if (toSave.hasAnnotation("nosave")) {
Actions.PerformActionCatchErrors(toSave, null, "Restore");
return "";
}
// Save frame that is not local through the Networking classes
// TODO
if (!toSave.isLocal()) {
return FrameShare.getInstance().saveFrame(toSave);
}
/* Format the frame if it has the autoFormat tag */
if (toSave.hasAnnotation("autoformat")) {
Actions.PerformActionCatchErrors(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, dont 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)) {
writer = new ExpWriter();
savedVersion = ExpReader.getVersion(fullPath);
} else {
writer = new KMSWriter();
savedVersion = KMSReader.getVersion(fullPath);
}
// Check if the frame doesnt 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
if (savedVersion > toSave.getVersion()
&& !toSave.getFramesetName().equalsIgnoreCase(
MessageBay.MESSAGES_FRAMESET_NAME)) {
// 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);
originalMessage.setText(original.getName()
+ " was updated by another user.");
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);
} else if (checkBackup
&& ItemUtils.ContainsExactTag(toSave.getItems(),
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());
nextnum = 1;
} catch (Exception e1) {
e1.printStackTrace();
}
// e.printStackTrace();
}
if (nextnum > 0) {
original.setFrameset(oldFramesetName);
original.setFrameNumber(nextnum);
original.setPermission(new PermissionPair(UserAppliedPermission.copy));
original.change();
SaveFrame(original, false, false);
}
Item i = ItemUtils.FindExactTag(toSave.getItems(),
ItemUtils.TAG_BACKUP);
i.setLink(original.getName());
toSave.setFrameNumber(orignum);
ResumeCache();
}
// Update general stuff about frame
setSavedProperties(toSave);
// int oldMode = FrameGraphics.getMode();
// if (oldMode != FrameGraphics.MODE_XRAY)
// FrameGraphics.setMode(FrameGraphics.MODE_XRAY, true);
writer.writeFrame(toSave);
// FrameGraphics.setMode(oldMode, true);
toSave.setSaved();
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;
}
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 originalFrameName = toSave.getFramesetName();
//System.out.println(originalFrameName + " : " + toSave.getPath());
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);
}
/**
* 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) return false;
return toCheck.getPath().equals(PROFILE_PATH);
// return toCheck.getFramesetName()
// .equalsIgnoreCase(UserSettings.ProfileName);
}
public static Frame LoadProfile(String userName)
{
return LoadFrame(userName + "1");
}
public static Frame CreateNewProfile(String username) throws Exception {
Frame profile = CreateFrameset(username, PROFILE_PATH, true);
FrameUtils.CreateDefaultProfile(username, profile);
return profile;
}
/**
* 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
* @return The name of the first Frame in the newly created Frameset (the .1
* frame)
*/
public static Frame CreateNewFrameset(String name) 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);
if (newFrame == null) {
// Cant create directories if the path is readonly or there is no
// space available
newFrame = FrameIO.CreateFrameset(name, FrameIO.FRAME_PATH);
}
if (newFrame == null) {
// TODO handle running out of disk space here
}
return newFrame;
}
/**
*
* @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.get()) {
if ((new File(path + framesetName)).exists())
return true;
}
return false;
}
public static Frame CreateFrameset(String frameset, String path, boolean recreate) throws Exception
{
String conversion = frameset + " --> ";
if (!isValidFramesetName(frameset)) throw new Exception("Invalid frameset name");
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 the directory couldnt be created, then there is something
// wrong... ie. The disk is full.
if (!dir.mkdirs()) {
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();
base.reset();
base.resetDateCreated();
base.setFrameset(frameset);
base.setFrameNumber(0);
base.setTitle(base.getFramesetName() + "0");
base.setPath(path);
base.change();
base.setOwner(UserSettings.UserName.get());
SaveFrame(base, false);
base.reset();
base.resetDateCreated();
base.setFrameNumber(1);
base.setTitle(frameset);
base.change();
base.setOwner(UserSettings.UserName.get());
SaveFrame(base, true);
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);
}
public static boolean moveFrameset(String framesetName,
String destinationFolder) {
if (!FrameIO.canAccessFrameset(framesetName))
return false;
// Clear the cache
_Cache.clear();
// Search all the available directories for the directory
for (String path : FolderSettings.FrameDirs.get()) {
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()) {
dest = new File(destPath + ++copyNumber + File.separator);
}
if (!framesetDirectory.renameTo(dest)) {
for (File f : framesetDirectory.listFiles()) {
if (!f.delete())
return false;
}
if (!framesetDirectory.delete())
return false;
}
return true;
}
}
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.get()) {
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 Exception {
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();
}
// } else {
//
//
//
// MessageBay
// .errorMessage("Recieved save request for unknown public frame: "
// + frameName);
// }
}
public static void setSavedProperties(Frame toSave) {
toSave.setLastModifyDate(Formatter.getDateTime());
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);
}
}