source: trunk/src/org/expeditee/gui/FrameIO.java@ 307

Last change on this file since 307 was 307, checked in by ra33, 16 years ago

Can navigate with PgUp PgDn Home and End with remote framesets

File size: 40.9 KB
Line 
1package org.expeditee.gui;
2
3import java.io.BufferedReader;
4import java.io.BufferedWriter;
5import java.io.File;
6import java.io.FileInputStream;
7import java.io.FileNotFoundException;
8import java.io.FileOutputStream;
9import java.io.FileReader;
10import java.io.FileWriter;
11import java.io.IOException;
12import java.sql.Time;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.LinkedList;
16
17import org.expeditee.agents.ExistingFramesetException;
18import org.expeditee.io.Conversion;
19import org.expeditee.io.ExpReader;
20import org.expeditee.io.ExpWriter;
21import org.expeditee.io.FrameReader;
22import org.expeditee.io.FrameWriter;
23import org.expeditee.io.KMSReader;
24import org.expeditee.io.KMSWriter;
25import org.expeditee.io.Logger;
26import org.expeditee.items.Item;
27import org.expeditee.items.ItemUtils;
28import org.expeditee.items.Permission;
29import org.expeditee.items.Text;
30import org.expeditee.network.FrameShare;
31import org.expeditee.stats.SessionStats;
32
33/**
34 * This class provides static methods for all saving and loading of Frames
35 * to\from disk. This class also handles any caching of previously loaded
36 * Frames.
37 *
38 * @author jdm18
39 *
40 */
41public class FrameIO {
42
43 private static final char FRAME_NAME_LAST_CHAR = 'A';
44
45 public static void changeParentFolder(String newFolder) {
46 PARENT_FOLDER = newFolder;
47 PUBLIC_PATH = PARENT_FOLDER + "public" + File.separator;
48 FRAME_PATH = PARENT_FOLDER + "framesets" + File.separator;
49 MESSAGES_PATH = PARENT_FOLDER + "messages" + File.separator;
50 TRASH_PATH = PARENT_FOLDER + "trash" + File.separator;
51 IMAGES_PATH = PARENT_FOLDER + IMAGES_FOLDER;
52 HELP_PATH = PARENT_FOLDER + "documentation" + File.separator;
53 PROFILE_PATH = PARENT_FOLDER + "profiles" + File.separator;
54 EXPORTS_DIR = PARENT_FOLDER + "exports" + File.separator;
55 STATISTICS_DIR = PARENT_FOLDER + "statistics" + File.separator;
56 LOGS_DIR = PARENT_FOLDER + "logs" + File.separator;
57 }
58
59 /**
60 * The default location for storing the framesets. Each frameset has its own
61 * subdirectory in this directory.
62 */
63 public static String IMAGES_FOLDER = "images" + File.separator;
64
65 public static String TRASH_PATH;
66
67 public static String PARENT_FOLDER;
68
69 public static String FRAME_PATH;
70
71 public static String MESSAGES_PATH;
72
73 public static String PUBLIC_PATH;
74
75 public static String IMAGES_PATH;
76
77 public static String HELP_PATH;
78
79 public static String PROFILE_PATH;
80
81 public static String EXPORTS_DIR;
82
83 public static String STATISTICS_DIR;
84
85 public static String LOGS_DIR;
86
87 private static final String INF_FILENAME = "frame.inf";
88
89 public static final String ILLEGAL_CHARS = ";:\\/?";
90
91 public static final int MAX_NAME_LENGTH = 64;
92
93 public static final int MAX_CACHE = 100;
94
95 private static HashMap<String, Frame> _Cache = new FrameCache();
96
97 // private static HashMap<String, String> _FramesetNameCache = new
98 // HashMap<String, String>();
99
100 private static boolean ENABLE_CACHE = true;
101
102 private static boolean _UseCache = true;
103
104 private static boolean _SuspendedCache = false;
105
106 // All methods are static, this should not be instantiated
107 private FrameIO() {
108 }
109
110 public static boolean isCacheOn() {
111 return _UseCache && ENABLE_CACHE;
112 }
113
114 public static void Precache(String framename) {
115 // if the cache is turned off, do nothing
116 if (!isCacheOn())
117 return;
118
119 // if the frame is already in the cache, do nothing
120 if (_Cache.containsKey(framename.toLowerCase()))
121 return;
122
123 // otherwise, load the frame and put it in the cache
124 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Precaching " + framename + ".");
125
126 // do not display errors encountered to the user
127 // (they will be shown at load time)
128 MessageBay.supressMessages(true);
129 // loading automatically caches the frame is caching is turned on
130 LoadFromDisk(framename, null, false);
131 MessageBay.supressMessages(false);
132 }
133
134 /**
135 * Checks if a string is a representation of a positive integer.
136 *
137 * @param s
138 * @return true if s is a positive integer
139 */
140 public static boolean isPositiveInteger(String s) {
141 if (s == null || s.length() == 0)
142 return false;
143
144 for (int i = 0; i < s.length(); i++) {
145 if (!Character.isDigit(s.charAt(i)))
146 return false;
147 }
148 return true;
149 }
150
151 public static Frame LoadFrame(String frameName) {
152 return LoadFrame(frameName, null, false);
153 }
154
155 public static Frame LoadFrame(String frameName, String path) {
156 return LoadFrame(frameName, path, false);
157 }
158
159 public static Frame LoadFrame(String frameName, String path,
160 boolean ignoreAnnotations) {
161 if (!isValidFrameName(frameName))
162 return null;
163
164 String frameNameLower = frameName.toLowerCase();
165 // first try reading from cache
166 if (isCacheOn() && _Cache.containsKey(frameNameLower)) {
167 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
168 + " from cache.");
169 Frame frame = _Cache.get(frameNameLower);
170 return frame;
171 }
172
173 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
174 + " from disk.");
175
176 return LoadFromDisk(frameName, path, ignoreAnnotations);
177 }
178
179 public static BufferedReader LoadPublicFrame(String frameName) {
180 String fullPath = FrameIO.getFrameFullPathName(PUBLIC_PATH, frameName);
181
182 if(fullPath == null)
183 return null;
184
185 File frameFile = new File(fullPath);
186 if (frameFile.exists() && frameFile.canRead()) {
187 try {
188 return new BufferedReader(new FileReader(frameFile));
189 } catch (FileNotFoundException e) {
190 e.printStackTrace();
191 }
192 }
193 return null;
194 }
195
196 private static Frame LoadFromDisk(String framename, String knownPath,
197 boolean ignoreAnnotations) {
198 Frame loaded = null;
199
200 if (knownPath != null) {
201 loaded = LoadKnowPath(knownPath, framename);
202 } else {
203
204 for (String path : UserSettings.FrameDirs) {
205 loaded = LoadKnowPath(path, framename);
206 if (loaded != null) {
207 break;
208 }
209 }
210 }
211
212 if (loaded == null && FrameShare.getInstance() != null) {
213 loaded = FrameShare.getInstance().loadFrame(framename, knownPath);
214 }
215
216 if (loaded != null) {
217 FrameUtils.Parse(loaded, true, ignoreAnnotations);
218 }
219
220 return loaded;
221 }
222
223 /**
224 * Gets a list of all the framesets available to the user
225 *
226 * @return a string containing a list of all the available framesets on
227 * separate lines
228 */
229 public static String getFramesetList() {
230 StringBuffer list = new StringBuffer();
231
232 for (String path : UserSettings.FrameDirs) {
233 File files = new File(path);
234 if (!files.exists())
235 continue;
236 for (File f : (new File(path)).listFiles()) {
237 if (f.isDirectory()) {
238 list.append(f.getName()).append('\n');
239 }
240 }
241 }
242 // remove the final new line char
243 list.deleteCharAt(list.length() - 1);
244 return list.toString();
245 }
246
247 /**
248 * Gets the full path and file name of the frame.
249 *
250 * @param path-
251 * the directory in which to look for the frameset containing the
252 * frame.
253 * @param frameName-
254 * the name of the frame for which the path is being requested.
255 * @return null if the frame can not be located.
256 */
257 public static synchronized String getFrameFullPathName(String path,
258 String frameName) {
259 String source = path + Conversion.getFramesetName(frameName)
260 + File.separator;
261
262 File tester = new File(source);
263 if (!tester.exists())
264 return null;
265
266 // check for the new file name format
267 String fullPath = source + Conversion.getFrameNumber(frameName)
268 + ExpReader.EXTENTION;
269 tester = new File(fullPath);
270
271 if (tester.exists())
272 return fullPath;
273
274 // check for oldfile name format
275 fullPath = source + Conversion.getFramesetName(frameName) + "."
276 + Conversion.getFrameNumber(frameName);
277 tester = new File(fullPath);
278
279 if (tester.exists())
280 return fullPath;
281
282 return null;
283 }
284
285 public static boolean canAccessFrame(String frameName) {
286 Frame current = DisplayIO.getCurrentFrame();
287 // Just incase the current frame is not yet saved...
288 if (frameName.equals(current.getName())) {
289 FrameIO.SaveFrame(current, false, false);
290 current.change();
291 return true;
292 }
293
294 for (String path : UserSettings.FrameDirs) {
295 if (getFrameFullPathName(path, frameName) != null)
296 return true;
297 }
298 return false;
299 }
300
301 public static Collection<String> searchFrame(String frameName,
302 String pattern, String path) {
303 String fullPath = null;
304 if (path == null) {
305 for (String possiblePath : UserSettings.FrameDirs) {
306 fullPath = getFrameFullPathName(possiblePath, frameName);
307 if (fullPath != null)
308 break;
309 }
310 } else {
311 fullPath = getFrameFullPathName(path, frameName);
312 }
313 // If the frame was not located return null
314 if (fullPath == null)
315 return null;
316 Collection<String> results = new LinkedList<String>();
317 // Open the file and search the text items
318 try {
319 BufferedReader reader = new BufferedReader(new FileReader(fullPath));
320 String next;
321 while (reader.ready() && ((next = reader.readLine()) != null)) {
322 if (next.startsWith("T")) {
323 String toSearch = next.substring(2);
324 if (toSearch.toLowerCase().contains(pattern))
325 results.add(toSearch);
326 }
327 }
328 } catch (FileNotFoundException e) {
329 e.printStackTrace();
330 return null;
331 } catch (IOException e) {
332 e.printStackTrace();
333 }
334 return results;
335 }
336
337 private static Frame LoadKnowPath(String path, String frameName) {
338 String fullPath = getFrameFullPathName(path, frameName);
339 if (fullPath == null)
340 return null;
341
342 try {
343 FrameReader reader;
344
345 if (fullPath.endsWith(ExpReader.EXTENTION)) {
346 reader = new ExpReader(frameName);
347 } else {
348 reader = new KMSReader();
349 }
350 Frame frame = reader.readFrame(fullPath);
351
352 if (frame == null) {
353 MessageBay.errorMessage("Error: " + frameName
354 + " could not be successfully loaded.");
355 return null;
356 }
357
358 frame.setPath(path);
359
360 // do not put 0 frames or virtual frames into the cache
361 if (_Cache.size() > MAX_CACHE)
362 _Cache.clear();
363
364 if (frame.getNumber() > 0 && isCacheOn())
365 _Cache.put(frameName.toLowerCase(), frame);
366
367 return frame;
368 } catch (IOException ioe) {
369 ioe.printStackTrace();
370 Logger.Log(ioe);
371 } catch (Exception e) {
372 e.printStackTrace();
373 Logger.Log(e);
374 MessageBay.errorMessage("Error: " + frameName
375 + " could not be successfully loaded.");
376 }
377
378 return null;
379 }
380
381 public static void Reload() {
382 // disable cache
383 boolean cache = _UseCache;
384
385 _UseCache = false;
386 Frame fresh = FrameIO.LoadFrame(DisplayIO.getCurrentFrame().getName());
387 _UseCache = cache;
388 if (_Cache.containsKey(fresh.getName().toLowerCase()))
389 addToCache(fresh);
390 DisplayIO.setCurrentFrame(fresh, false);
391 }
392
393 public static Frame LoadPrevious(Frame current) {
394 checkTDFC(current);
395
396 // the current name and number
397 String name = current.getFramesetName();
398 int num = current.getNumber() - 1;
399
400 // loop until a frame that exists is found
401 for (; num >= 0; num--) {
402 Frame f = LoadFrame(name + num, current.getPath());
403 if (f != null)
404 return f;
405 }
406
407 // if we did not find another Frame then this one must be the last one
408 // in the frameset
409 MessageBay
410 .displayMessageOnce("This is the first frame in the frameset");
411 return null;
412 }
413
414 /**
415 * Returns the next Frame in the current Frameset (The Frame with the next
416 * highest Frame number) If the current Frame is the last one in the
417 * Frameset, or an error occurs then null is returned.
418 *
419 * @return The Frame after this one in the current frameset, or null
420 */
421 public static Frame LoadNext(Frame current) {
422 checkTDFC(current);
423
424 // the current name and number
425 int num = current.getNumber() + 1;
426 int max = num + 1;
427 String name = current.getFramesetName();
428
429 // read the maximum from the INF file
430 try {
431 max = ReadINF(current.getPath(), current.getFramesetName(), false);
432 } catch (IOException ioe) {
433 MessageBay.errorMessage("Error loading INF file for frameset '"
434 + name + "'");
435 return null;
436 }
437
438 // loop until a frame that exists is found
439 for (; num <= max; num++) {
440 Frame f = LoadFrame(name + num, current.getPath());
441 if (f != null)
442 return f;
443 }
444
445 // if we did not find another Frame then this one must be the last one
446 // in the frameset
447 MessageBay.displayMessageOnce("This is the last frame in the frameset");
448 return null;
449 }
450
451 /**
452 * This method checks if the current frame has just been created with TDFC.
453 * If it has the frame is saved regardless of whether it has been edited or
454 * not and the TDFC item property is cleared. This is to ensure that the
455 * link is saved on the parent frame.
456 *
457 * @param current
458 */
459 public static void checkTDFC(Frame current) {
460 if (FrameUtils.getTdfcItem() != null) {
461 FrameUtils.setTdfcItem(null);
462 current.change();
463 }
464 }
465
466 public static Frame LoadLast(String framesetName, String path) {
467 // read the maximum from the INF file
468 int max;
469 try {
470 max = ReadINF(path, framesetName, false);
471 } catch (IOException ioe) {
472 MessageBay.errorMessage("Error loading INF file for frameset '"
473 + framesetName + "'");
474 return null;
475 }
476
477 // loop backwards until a frame that exists is found
478 for (int num = max; num > 0; num--) {
479 Frame f = LoadFromDisk(framesetName + num, path, false);
480 if (f != null)
481 return f;
482 }
483
484 // if we did not find another Frame then this one must be the last one
485 // in the frameset
486 MessageBay.displayMessage("This is the last frame in the frameset");
487 return null;
488 }
489
490 public static Frame LoadZero(String framesetName, String path) {
491 return LoadFrame(framesetName + 0);
492 }
493
494 public static Frame LoadZero() {
495 Frame current = DisplayIO.getCurrentFrame();
496 return LoadZero(current.getFramesetName(), current.getPath());
497 }
498
499 public static Frame LoadLast() {
500 Frame current = DisplayIO.getCurrentFrame();
501 return LoadLast(current.getFramesetName(), current.getPath());
502 }
503
504 public static Frame LoadNext() {
505 return LoadNext(DisplayIO.getCurrentFrame());
506 }
507
508 public static Frame LoadPrevious() {
509 return LoadPrevious(DisplayIO.getCurrentFrame());
510 }
511
512 /**
513 * Deletes the given Frame on disk and removes the cached Frame if there is
514 * one. Also adds the deleted frame into the deletedFrames frameset.
515 *
516 * @param toDelete
517 * The Frame to be deleted
518 * @return The name the deleted frame was changed to, or null if the delete
519 * failed
520 */
521 public static String DeleteFrame(Frame toDelete) throws IOException,
522 SecurityException {
523 if (toDelete == null)
524 return null;
525
526 // Dont delete the zero frame
527 if (toDelete.getNumber() == 0) {
528 throw new SecurityException("Deleting a zero frame is illegal");
529 }
530
531 // Dont delete the zero frame
532 if (!toDelete.isLocal()) {
533 throw new SecurityException("Attempted to delete remote frame");
534 }
535
536 SaveFrame(toDelete);
537
538 // Copy deleted frames to the DeletedFrames frameset
539 // get the last used frame in the destination frameset
540 final String DELETED_FRAMES = "DeletedFrames";
541 int lastNumber = FrameIO.getLastNumber(DELETED_FRAMES);
542 String framePath;
543 try {
544 // create the new frameset
545 Frame one = FrameIO.CreateFrameset(DELETED_FRAMES, toDelete
546 .getPath());
547 framePath = one.getPath();
548 lastNumber = 0;
549 } catch (Exception e) {
550 Frame zero = FrameIO.LoadFrame(DELETED_FRAMES + "0");
551 framePath = zero.getPath();
552 }
553
554 // get the fill path to determine which file version it is
555 String source = getFrameFullPathName(toDelete.getPath(), toDelete
556 .getName());
557
558 String oldFrameName = toDelete.getName().toLowerCase();
559 // Now save the frame in the new location
560 toDelete.setFrameset(DELETED_FRAMES);
561 toDelete.setFrameNumber(lastNumber + 1);
562 toDelete.setPath(framePath);
563 ForceSaveFrame(toDelete);
564
565 if (_Cache.containsKey(oldFrameName))
566 _Cache.remove(oldFrameName);
567
568 File del = new File(source);
569
570 java.io.FileInputStream ff = new java.io.FileInputStream(del);
571 ff.close();
572
573 if (del.delete()) {
574 return toDelete.getName();
575 }
576
577 return null;
578 }
579
580 /**
581 * Creates a new Frame in the given frameset and assigns it the given Title,
582 * which can be null. The newly created Frame is a copy of the frameset's .0
583 * file with the number updated based on the last recorded Frame name in the
584 * frameset's INF file.
585 *
586 * @param frameset
587 * The frameset to create the new Frame in
588 * @param frameTitle
589 * The title to assign to the newly created Frame (can be NULL).
590 * @return The newly created Frame.
591 */
592 public static synchronized Frame CreateFrame(String frameset,
593 String frameTitle, String templateFrame) throws RuntimeException {
594
595 if (!FrameIO.isValidFramesetName(frameset)) {
596 throw new RuntimeException(frameset
597 + " is not a valid frameset name");
598 }
599
600 int next = -1;
601
602 // disable caching of 0 frames
603 SuspendCache();
604 String zeroFrameName = frameset + "0";
605 Frame destFramesetZero = LoadFrame(zeroFrameName);
606 if (destFramesetZero == null) {
607 throw new RuntimeException(zeroFrameName + " could not be found");
608 }
609
610 Frame template = null;
611 if (templateFrame == null) {
612 // load in frame.0
613 template = destFramesetZero;
614 } else {
615 template = LoadFrame(templateFrame);
616 if (template == null) {
617 throw new RuntimeException("LinkTemplate " + templateFrame
618 + " could not be found");
619 }
620 }
621
622 ResumeCache();
623
624 // read the next number from the INF file
625 try {
626 next = ReadINF(destFramesetZero.getPath(), frameset, true);
627 } catch (IOException ioe) {
628 ioe.printStackTrace();
629 throw new RuntimeException("INF file could not be read");
630 }
631
632 // Remove the old frame from the cashe then add the new one
633 // TODO figure out some way that we can put both in the cache
634 _Cache.remove(template.getName().toLowerCase());
635 // set the number and title of the new frame
636 template.setName(frameset, ++next);
637 template.setTitle(frameTitle);
638 // _Cache.put(template.getName().toLowerCase(), template);
639
640 Logger.Log(Logger.SYSTEM, Logger.TDFC, "Creating new frame: "
641 + template.getName() + " from TDFC");
642
643 template.setOwner(UserSettings.Username);
644 template.resetDateCreated();
645 for (Item i : template.getItems()) {
646 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT))
647 i.setLink(null);
648 }
649
650 // do auto shrinking of the title IF not in twin frames mode
651 Item titleItem = template.getTitleItem();
652
653 if (!DisplayIO.isTwinFramesOn()) {
654 // BROOK: This had recursion!! Changed to avoid...
655 if ((titleItem.getX() + 1) < template.getNameItem().getX()) {
656 while (titleItem.getBoundsWidth() + titleItem.getX() > template
657 .getNameItem().getX()) {
658 titleItem.setSize(titleItem.getSize() - 1);
659 }
660 } else {
661 System.out.println("Bad title x position: " + titleItem.getX());
662 }
663 }
664 return template;
665 }
666
667 public static void DisableCache() {
668 _UseCache = false;
669 }
670
671 public static void EnableCache() {
672 _UseCache = true;
673 }
674
675 public static void SuspendCache() {
676 if (_UseCache) {
677 DisableCache();
678 _SuspendedCache = true;
679 } else {
680 _SuspendedCache = false;
681 }
682 }
683
684 public static void ResumeCache() {
685 if (_SuspendedCache) {
686 EnableCache();
687 _SuspendedCache = false;
688 }
689 }
690
691 public static void RefreshCasheImages() {
692 SuspendCache();
693 for (Frame f : _Cache.values())
694 f.setBuffer(null);
695 ResumeCache();
696 }
697
698 /**
699 * Creates a new frameset using the given name. This includes creating a new
700 * subdirectory in the <code>FRAME_PATH</code> directory, Copying over the
701 * default.0 frame from the default frameset, copying the .0 Frame to make a
702 * .1 Frame, and creating the frameset's INF file.
703 *
704 * @param frameset
705 * The name of the Frameset to create
706 * @return The first Frame of the new Frameset (Frame.1)
707 */
708 public static Frame CreateFrameset(String frameset, String path)
709 throws Exception {
710 return CreateFrameset(frameset, path, false);
711 }
712
713 /**
714 * Tests if the given String is a 'proper' framename, that is, the String
715 * must begin with a character, end with a number with 0 or more letters and
716 * numbers in between.
717 *
718 * @param frameName
719 * The String to test for validity as a frame name
720 * @return True if the given framename is proper, false otherwise.
721 */
722 public static boolean isValidFrameName(String frameName) {
723
724 if (frameName == null || frameName.length() < 2)
725 return false;
726
727 int lastCharIndex = frameName.length() - 1;
728 // String must begin with a letter and end with a digit
729 if (!Character.isLetter(frameName.charAt(0))
730 || !Character.isDigit(frameName.charAt(lastCharIndex)))
731 return false;
732
733 // All the characters between first and last must be letters
734 // or digits
735 for (int i = 1; i < lastCharIndex; i++) {
736 if (!Character.isLetterOrDigit(frameName.charAt(i)))
737 return false;
738 }
739 return true;
740 }
741
742 /**
743 * Saves the given Frame to disk in the corresponding frameset directory.
744 * This is the same as calling SaveFrame(toSave, true)
745 *
746 * @param toSave
747 * The Frame to save to disk
748 */
749 public static String SaveFrame(Frame toSave) {
750 return SaveFrame(toSave, true);
751 }
752
753 public static String SaveFrame(Frame toSave, boolean inc) {
754 return SaveFrame(toSave, inc, true);
755 }
756
757 /**
758 * Saves the given Frame to disk in the corresponding frameset directory, if
759 * inc is true then the saved frames counter is incremented, otherwise it is
760 * untouched.
761 *
762 * @param toSave
763 * The Frame to save to disk
764 * @param inc
765 * True if the saved frames counter should be incremented, false
766 * otherwise.
767 * @param checkBackup
768 * True if the frame should be checked for the back up tag
769 */
770 public static String SaveFrame(Frame toSave, boolean inc,
771 boolean checkBackup) {
772
773 // TODO When loading a frame maybe append onto the event history too-
774 // with a
775 // break to indicate the end of a session
776
777 if (toSave == null || !toSave.hasChanged() || toSave.isSaved()) {
778 SessionStats.NewFrameSession();
779 return "";
780 }
781
782 // Dont save if the frame is protected and it exists
783 if (checkBackup && toSave.isReadOnly()) {
784 _Cache.remove(toSave.getName().toLowerCase());
785 SessionStats.NewFrameSession();
786 return "";
787 }
788
789 // Save frame that is not local through the Networking classes
790 // TODO
791 if (!toSave.isLocal()) {
792 return FrameShare.getInstance().saveFrame(toSave);
793 }
794
795 /**
796 * Get the full path only to determine which format to use for saving
797 * the frame. At this stage use Exp format for saving Exp frames only.
798 * Later this will be changed so that KMS frames will be updated to the
799 * Exp format.
800 */
801 String fullPath = getFrameFullPathName(toSave.getPath(), toSave
802 .getName());
803
804 // Check if the frame exists
805 if (checkBackup && fullPath == null) {
806 // The first time a frame with the backup tag is saved, dont back it
807 // up
808 checkBackup = false;
809 }
810
811 FrameWriter writer = null;
812 int savedVersion;
813 try {
814 // if its a new frame or an existing Exp frame...
815 if (fullPath == null || fullPath.endsWith(ExpReader.EXTENTION)) {
816 writer = new ExpWriter();
817 savedVersion = ExpReader.getVersion(fullPath);
818 } else {
819 writer = new KMSWriter();
820 savedVersion = KMSReader.getVersion(fullPath);
821 }
822 // Check if we are trying to save an out of date version
823 if (savedVersion > toSave.getVersion()
824 && !toSave.getFramesetName().equalsIgnoreCase(
825 MessageBay.MESSAGES_FRAMESET_NAME)) {
826 // remove this frame from the cache if it is there
827 // This will make sure links to the original are set correctly
828 _Cache.remove(toSave.getName().toLowerCase());
829 int nextnum = ReadINF(toSave.getPath(), toSave
830 .getFramesetName(), false) + 1;
831 SuspendCache();
832 Frame original = LoadFrame(toSave.getName());
833 toSave.setFrameNumber(nextnum);
834 ResumeCache();
835 // Put the modified version in the cache
836 addToCache(toSave);
837 // Show the messages alerting the user
838 Text originalMessage = new Text(-1);
839 originalMessage.setColor(MessageBay.ERROR_COLOR);
840 originalMessage.setText(original.getName()
841 + " was updated by another user.");
842 originalMessage.setLink(original.getName());
843 Text yourMessage = new Text(-1);
844 yourMessage.setColor(MessageBay.ERROR_COLOR);
845 yourMessage.setText("Your version was renamed "
846 + toSave.getName());
847 yourMessage.setLink(toSave.getName());
848 MessageBay.displayMessage(originalMessage);
849 MessageBay.displayMessage(yourMessage);
850 } else if (checkBackup
851 && ItemUtils.ContainsExactTag(toSave.getItems(),
852 ItemUtils.TAG_BACKUP)) {
853 SuspendCache();
854 Frame original = LoadFrame(toSave.getName());
855 if (original == null)
856 original = toSave;
857 int orignum = original.getNumber();
858 int nextnum = ReadINF(toSave.getPath(), toSave
859 .getFramesetName(), false) + 1;
860
861 original.setFrameNumber(nextnum);
862 original.setPermission(Permission.copy);
863 original.change();
864 SaveFrame(original, false, false);
865
866 Item i = ItemUtils.FindExactTag(toSave.getItems(),
867 ItemUtils.TAG_BACKUP);
868 i.setLink(original.getName());
869 toSave.setFrameNumber(orignum);
870 ResumeCache();
871 }
872 // Update general stuff about frame
873 setSavedProperties(toSave);
874
875 // int oldMode = FrameGraphics.getMode();
876 // if (oldMode != FrameGraphics.MODE_XRAY)
877 // FrameGraphics.setMode(FrameGraphics.MODE_XRAY, true);
878 writer.writeFrame(toSave);
879 // FrameGraphics.setMode(oldMode, true);
880 toSave.setSaved();
881 if (inc) {
882 SessionStats.SavedFrame(toSave.getName());
883 }
884
885 // avoid out-of-sync frames (when in TwinFrames mode)
886 if (_Cache.containsKey(toSave.getName().toLowerCase()))
887 addToCache(toSave);
888
889 Logger.Log(Logger.SYSTEM, Logger.SAVE, "Saving " + toSave.getName()
890 + " to disk.");
891
892 // check that the INF file is not out of date
893 int last = ReadINF(toSave.getPath(), toSave.getFramesetName(),
894 false);
895 if (last <= toSave.getNumber())
896 WriteINF(toSave.getPath(), toSave.getFramesetName(), toSave
897 .getName());
898
899 // check if this was the profile frame (and thus needs
900 // re-parsing)
901 if (isProfileFrame(toSave)) {
902 Frame profile = FrameIO.LoadFrame(toSave.getFramesetName()
903 + "1");
904 assert (profile != null);
905 FrameUtils.ParseProfile(profile);
906 }
907 } catch (IOException ioe) {
908 ioe.getStackTrace();
909 Logger.Log(ioe);
910 return null;
911 }
912
913 return writer.getFileContents();
914 }
915
916 /**
917 * @param toAdd
918 */
919 public static void addToCache(Frame toAdd) {
920 _Cache.put(toAdd.getName().toLowerCase(), toAdd);
921 }
922
923 /**
924 * Checks if a frame is in the current user profile frameset.
925 *
926 * @param toCheck
927 * the frame to check
928 * @return true if the frame is in the current user profile frameset
929 */
930 public static boolean isProfileFrame(Frame toCheck) {
931 if (toCheck.getNumber() == 0)
932 return false;
933 return toCheck.getFramesetName()
934 .equalsIgnoreCase(UserSettings.Username);
935 }
936
937 public static Frame LoadProfile(String userName) {
938 return LoadFrame(userName + "1");
939 }
940
941 public static Frame CreateNewProfile(String username) throws Exception {
942 Frame profile = CreateFrameset(username, PROFILE_PATH, true);
943 FrameUtils.CreateDefaultProfile(profile);
944 return profile;
945 }
946
947 /**
948 * Reads the INF file that corresponds to the given Frame name
949 *
950 * @param framename
951 * The Frame to lookup the INF file for
952 * @throws IOException
953 * Any exceptions encountered by the BufferedReader used to read
954 * the INF.
955 */
956 public static int ReadINF(String path, String frameset, boolean update)
957 throws IOException {
958 assert (!frameset.endsWith("."));
959 try {
960 // read INF
961 BufferedReader reader;
962 try {
963 // Check on the local drive
964 reader = new BufferedReader(new FileReader(path
965 + frameset.toLowerCase() + File.separator
966 + INF_FILENAME));
967 } catch (Exception e) {
968 reader = new BufferedReader(new FileReader(path
969 + frameset.toLowerCase() + File.separator
970 + frameset.toLowerCase() + ".inf"));
971 }
972 String inf = reader.readLine();
973 reader.close();
974
975 int next = Conversion.getFrameNumber(inf);
976 // update INF file
977 if (update) {
978 try {
979 WriteINF(path, frameset, frameset + (next + 1));
980 } catch (IOException ioe) {
981 ioe.printStackTrace();
982 Logger.Log(ioe);
983 }
984 }
985 return next;
986 } catch (Exception e) {
987 }
988
989 // Check peers
990 return FrameShare.getInstance().getInfNumber(path, frameset, update);
991 }
992
993 /**
994 * Writes the given String out to the INF file corresponding to the current
995 * frameset.
996 *
997 * @param toWrite
998 * The String to write to the file.
999 * @throws IOException
1000 * Any exception encountered by the BufferedWriter.
1001 */
1002 public static void WriteINF(String path, String frameset, String frameName)
1003 throws IOException {
1004 try {
1005 assert (!frameset.endsWith("."));
1006
1007 path += frameset.toLowerCase() + File.separator + INF_FILENAME;
1008
1009 BufferedWriter writer = new BufferedWriter(new FileWriter(path));
1010 writer.write(frameName);
1011 writer.close();
1012 } catch (Exception e) {
1013
1014 }
1015 }
1016
1017 public static boolean FrameIsCached(String name) {
1018 return _Cache.containsKey(name);
1019 }
1020
1021 /**
1022 * Gets a frame from the cache.
1023 *
1024 * @param name
1025 * The frame to get from the cache
1026 *
1027 * @return The frame from cache. Null if not cached.
1028 */
1029 public static Frame FrameFromCache(String name) {
1030 return _Cache.get(name);
1031 }
1032
1033 public static String ConvertToValidFramesetName(String toValidate) {
1034 assert (toValidate != null && toValidate.length() > 0);
1035
1036 StringBuffer result = new StringBuffer();
1037
1038 if (Character.isDigit(toValidate.charAt(0))) {
1039 result.append(FRAME_NAME_LAST_CHAR);
1040 }
1041
1042 boolean capital = false;
1043 for (int i = 0; i < toValidate.length()
1044 && result.length() < MAX_NAME_LENGTH; i++) {
1045 char cur = toValidate.charAt(i);
1046
1047 // capitalize all characters after spaces
1048 if (Character.isLetterOrDigit(cur)) {
1049 if (capital) {
1050 capital = false;
1051 result.append(Character.toUpperCase(cur));
1052 } else
1053 result.append(cur);
1054 } else {
1055 capital = true;
1056 }
1057 }
1058 assert (result.length() > 0);
1059 int lastCharIndex = result.length() - 1;
1060 if (!Character.isLetter(result.charAt(lastCharIndex))) {
1061 if (lastCharIndex == MAX_NAME_LENGTH - 1)
1062 result.setCharAt(lastCharIndex, FRAME_NAME_LAST_CHAR);
1063 else
1064 result.append(FRAME_NAME_LAST_CHAR);
1065 }
1066
1067 assert (isValidFramesetName(result.toString()));
1068 return result.toString();
1069 }
1070
1071 public static Frame CreateNewFrame(Item linker) throws RuntimeException {
1072 String title = linker.getName();
1073
1074 String templateLink = linker.getAbsoluteLinkTemplate();
1075 String framesetLink = linker.getAbsoluteLinkFrameset();
1076 String frameset = (framesetLink != null ? framesetLink : DisplayIO
1077 .getCurrentFrame().getFramesetName());
1078
1079 Frame newFrame = FrameIO.CreateFrame(frameset, title, templateLink);
1080 return newFrame;
1081 }
1082
1083 /**
1084 * Creates a new Frameset on disk, including a .0, .1, and .inf files. The
1085 * Default.0 frame is copied to make the initial .0 and .1 Frames
1086 *
1087 * @param name
1088 * The Frameset name to use
1089 * @return The name of the first Frame in the newly created Frameset (the .1
1090 * frame)
1091 */
1092 public static Frame CreateNewFrameset(String name) throws Exception {
1093 String path = DisplayIO.getCurrentFrame().getPath();
1094
1095 // if current frameset is profile directory change it to framesets
1096 if (path.equals(FrameIO.PROFILE_PATH)) {
1097 path = FrameIO.FRAME_PATH;
1098 }
1099
1100 return FrameIO.CreateFrameset(name, path);
1101 }
1102
1103 /**
1104 *
1105 * @param frameset
1106 * @return
1107 */
1108 public static int getLastNumber(String frameset) { // Rob thinks it might
1109 // have been
1110 // GetHighestNumExFrame
1111 // TODO minimise the number of frames being read in!!
1112 int num = -1;
1113
1114 Frame zero = LoadFrame(frameset + "0");
1115
1116 // the frameset does not exist (or has no 0 frame)
1117 if (zero == null)
1118 return -1;
1119
1120 try {
1121 num = ReadINF(zero.getPath(), frameset, false);
1122 } catch (IOException e) {
1123 // TODO Auto-generated catch block
1124 // e.printStackTrace();
1125 }
1126
1127 /*
1128 * Michael doesnt think the code below is really needed... it will just
1129 * slow things down when we are reading frames over a network***** for (;
1130 * num >= 0; num--) { System.out.println("This code is loading frames to
1131 * find the highest existing frame..."); if (LoadFrame(frameset + num) !=
1132 * null) break; }
1133 */
1134
1135 return num;
1136 }
1137
1138 /**
1139 * Checks if a given frameset is accessable.
1140 *
1141 * @param framesetName
1142 * @return
1143 */
1144 public static Boolean canAccessFrameset(String framesetName) {
1145 framesetName = framesetName.toLowerCase();
1146 for (String path : UserSettings.FrameDirs) {
1147 if ((new File(path + framesetName)).exists())
1148 return true;
1149 }
1150 return false;
1151 }
1152
1153 public static Frame CreateFrameset(String frameset, String path,
1154 boolean recreate) throws Exception {
1155 String conversion = frameset + " --> ";
1156
1157 if (!isValidFramesetName(frameset)) {
1158 throw new Exception("Invalid frameset name");
1159 }
1160
1161 if (!recreate && FrameIO.canAccessFrameset(frameset)) {
1162 throw new ExistingFramesetException(frameset);
1163 }
1164
1165 conversion += frameset;
1166 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Frameset Name: "
1167 + conversion);
1168 conversion = frameset;
1169
1170 /**
1171 * TODO: Update this to exclude any\all invalid filename characters
1172 */
1173 // ignore annotation character
1174 if (frameset.startsWith("@"))
1175 frameset = frameset.substring(1);
1176
1177 conversion += " --> " + frameset;
1178 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Name: " + conversion);
1179
1180 // create the new Frameset directory
1181 File dir = new File(path + frameset.toLowerCase() + File.separator);
1182
1183 dir.mkdirs();
1184
1185 // create the new INF file
1186 try {
1187 WriteINF(path, frameset, frameset + '1');
1188 } catch (IOException ioe) {
1189 ioe.printStackTrace();
1190 Logger.Log(ioe);
1191 }
1192
1193 SuspendCache();
1194 // copy the default .0 and .1 files
1195 Frame base = null;
1196 try {
1197 base = LoadFrame(UserSettings.DefaultFrame);
1198 } catch (Exception e) {
1199 }
1200 // The frame may not be accessed for various reasons... in all these
1201 // cases just create a new one
1202 if (base == null) {
1203 base = new Frame();
1204 }
1205
1206 ResumeCache();
1207
1208 base.resetDateCreated();
1209 base.setFrameset(frameset);
1210 base.setFrameNumber(0);
1211 base.setTitle(base.getFramesetName() + "0");
1212 base.setPath(path);
1213 base.change();
1214 SaveFrame(base, false);
1215
1216 base.resetDateCreated();
1217 base.setFrameNumber(1);
1218 base.setTitle(frameset);
1219 base.change();
1220 SaveFrame(base, true);
1221
1222 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Created new frameset: "
1223 + frameset);
1224
1225 return base;
1226 }
1227
1228 /**
1229 * Tests if a frameset name is valid. That is it must begin and end with a
1230 * letter and contain only letters and digits in between.
1231 *
1232 * @param frameset
1233 * the name to be tested
1234 * @return true if the frameset name is valid
1235 */
1236 public static boolean isValidFramesetName(String frameset) {
1237 if (frameset == null) {
1238 return false;
1239 }
1240
1241 int nameLength = frameset.length();
1242 if (frameset.length() <= 0 || nameLength > MAX_NAME_LENGTH) {
1243 return false;
1244 }
1245
1246 int lastCharIndex = nameLength - 1;
1247
1248 if (!Character.isLetter(frameset.charAt(0))
1249 || !Character.isLetter(frameset.charAt(lastCharIndex)))
1250 return false;
1251
1252 for (int i = 1; i < lastCharIndex; i++) {
1253 if (!Character.isLetterOrDigit(frameset.charAt(i))) {
1254 return false;
1255 }
1256 }
1257 return true;
1258 }
1259
1260 public static boolean deleteFrameset(String framesetName) {
1261 return moveFrameset(framesetName, FrameIO.TRASH_PATH);
1262 }
1263
1264 public static boolean moveFrameset(String framesetName,
1265 String destinationFolder) {
1266 if (!FrameIO.canAccessFrameset(framesetName))
1267 return false;
1268 // Search all the available directories for the directory
1269 for (String path : UserSettings.FrameDirs) {
1270 String source = path + framesetName.toLowerCase() + File.separator;
1271 File framesetDirectory = new File(source);
1272 // Once we have found the directory move it to the trash
1273 if (framesetDirectory.exists()) {
1274 String destPath = destinationFolder
1275 + framesetName.toLowerCase();
1276 int copyNumber = 1;
1277 File dest = new File(destPath + File.separator);
1278 // Create the trash folder if it doesnt already exist
1279 if (!dest.getParentFile().exists())
1280 dest.mkdirs();
1281 // If a frameset with the same name is already in the trash add
1282 // a number to the end
1283 while (dest.exists()) {
1284 dest = new File(destPath + ++copyNumber + File.separator);
1285 }
1286 if (!framesetDirectory.renameTo(dest)) {
1287 for (File f : framesetDirectory.listFiles()) {
1288 if (!f.delete())
1289 return false;
1290 }
1291 if (!framesetDirectory.delete())
1292 return false;
1293 }
1294 return true;
1295 }
1296 }
1297 return false;
1298 }
1299
1300 public static boolean CopyFrameset(String framesetToCopy,
1301 String copiedFrameset) throws Exception {
1302 if (!FrameIO.canAccessFrameset(framesetToCopy))
1303 return false;
1304 if (FrameIO.canAccessFrameset(copiedFrameset))
1305 return false;
1306 // search through all the directories to find the frameset we are
1307 // copying
1308 for (String path : UserSettings.FrameDirs) {
1309 String source = path + framesetToCopy.toLowerCase()
1310 + File.separator;
1311 File framesetDirectory = new File(source);
1312 if (framesetDirectory.exists()) {
1313 // copy the frameset
1314 File copyFramesetDirectory = new File(path
1315 + copiedFrameset.toLowerCase() + File.separator);
1316 if (!copyFramesetDirectory.mkdirs())
1317 return false;
1318 // copy each of the frames
1319 for (File f : framesetDirectory.listFiles()) {
1320 // Ignore hidden files
1321 if (f.getName().charAt(0) == '.')
1322 continue;
1323 String copyPath = copyFramesetDirectory.getAbsolutePath()
1324 + File.separator + f.getName();
1325 FrameIO.copyFile(f.getAbsolutePath(), copyPath);
1326 }
1327 return true;
1328 }
1329 }
1330 return false;
1331 }
1332
1333 /**
1334 * Copies a file from one location to another.
1335 *
1336 * @param existingFile
1337 * @param newFileName
1338 * @throws Exception
1339 */
1340 public static void copyFile(String existingFile, String newFileName)
1341 throws Exception {
1342 FileInputStream is = new FileInputStream(existingFile);
1343 FileOutputStream os = new FileOutputStream(newFileName, false);
1344 int data;
1345 while ((data = is.read()) != -1) {
1346 os.write(data);
1347 }
1348 os.flush();
1349 os.close();
1350 is.close();
1351 }
1352
1353 /**
1354 * Saves a frame regardless of whether or not the frame is marked as having
1355 * been changed.
1356 *
1357 * @param frame
1358 * the frame to save
1359 * @return the contents of the frame or null if it could not be saved
1360 */
1361 public static String ForceSaveFrame(Frame frame) {
1362 frame.change();
1363 return SaveFrame(frame, false);
1364 }
1365
1366 public static boolean isValidLink(String frameName) {
1367 return frameName == null || isPositiveInteger(frameName)
1368 || isValidFrameName(frameName);
1369 }
1370
1371 public static void SavePublicFrame(String peerName, String frameName,
1372 int version, BufferedReader packetContents) {
1373 // TODO handle versioning - add version to the header
1374 // Remote user uploads version based on an old version
1375
1376 // Remove it from the cache so that next time it is loaded we get the up
1377 // todate version
1378 _Cache.remove(frameName.toLowerCase());
1379
1380 // Save to file
1381 String filename = PUBLIC_PATH + Conversion.getFramesetName(frameName)
1382 + File.separator + Conversion.getFrameNumber(frameName)
1383 + ExpReader.EXTENTION;
1384
1385 File file = new File(filename);
1386 // Ensure the file exists
1387 if (file.exists()) {
1388 // Check the versions
1389 int savedVersion = ExpReader.getVersion(filename);
1390
1391 if (savedVersion > version) {
1392 // remove this frame from the cache if it is there
1393 // This will make sure links to the original are set correctly
1394 // _Cache.remove(frameName.toLowerCase());
1395
1396 int nextNum = 0;
1397 try {
1398 nextNum = ReadINF(PUBLIC_PATH, Conversion
1399 .getFramesetName(frameName), false) + 1;
1400 } catch (IOException e) {
1401 e.printStackTrace();
1402 }
1403
1404 String newName = Conversion.getFramesetName(frameName)
1405 + nextNum;
1406 filename = PUBLIC_PATH + Conversion.getFramesetName(frameName)
1407 + File.separator + nextNum + ExpReader.EXTENTION;
1408
1409 // Show the messages alerting the user
1410 Text originalMessage = new Text(-1);
1411 originalMessage.setColor(MessageBay.ERROR_COLOR);
1412 originalMessage.setText(frameName + " was edited by "
1413 + peerName);
1414 originalMessage.setLink(frameName);
1415 Text yourMessage = new Text(-1);
1416 yourMessage.setColor(MessageBay.ERROR_COLOR);
1417 yourMessage.setText("Their version was renamed " + newName);
1418 yourMessage.setLink(newName);
1419 MessageBay.displayMessage(originalMessage);
1420 MessageBay.displayMessage(yourMessage);
1421
1422 Frame editedFrame = FrameIO.LoadFrame(frameName);
1423
1424 FrameShare.getInstance().sendMessage(
1425 frameName + " was recently edited by "
1426 + editedFrame.getLastModifyUser(), peerName);
1427 FrameShare.getInstance().sendMessage(
1428 "Your version was renamed " + newName, peerName);
1429 }
1430 }
1431
1432 // Save the new version
1433 try {
1434 FileWriter fw = new FileWriter(file);
1435 String nextLine = null;
1436 while ((nextLine = packetContents.readLine()) != null) {
1437 fw.write(nextLine + '\n');
1438 }
1439 fw.flush();
1440 fw.close();
1441 MessageBay.displayMessage("Saved remote frame: " + frameName);
1442 } catch (IOException e) {
1443 MessageBay.errorMessage("Error remote saving " + frameName + ": "
1444 + e.getMessage());
1445 e.printStackTrace();
1446 }
1447 // } else {
1448 //
1449 //
1450 //
1451 // MessageBay
1452 // .errorMessage("Recieved save request for unknown public frame: "
1453 // + frameName);
1454 // }
1455 }
1456
1457 public static void setSavedProperties(Frame toSave) {
1458 toSave.setLastModifyDate(Logger.EasyDateFormat("ddMMMyyyy:HHmm"));
1459 toSave.setLastModifyUser(UserSettings.Username);
1460 toSave.setVersion(toSave.getVersion() + 1);
1461 Time darkTime = new Time(SessionStats.getFrameDarkTime().getTime()
1462 + toSave.getDarkTime().getTime());
1463 Time activeTime = new Time(SessionStats.getFrameActiveTime().getTime()
1464 + toSave.getActiveTime().getTime());
1465 toSave.setDarkTime(darkTime);
1466 toSave.setActiveTime(activeTime);
1467 }
1468
1469}
Note: See TracBrowser for help on using the repository browser.