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

Last change on this file since 1251 was 1251, checked in by bln4, 5 years ago

Use UserSetting.Username instead of user.name system property; as UserSetting.Username gets updated.

File size: 57.3 KB
Line 
1/**
2 * FrameIO.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gui;
20
21import java.io.BufferedOutputStream;
22import java.io.BufferedReader;
23import java.io.BufferedWriter;
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.FileNotFoundException;
27import java.io.FileOutputStream;
28import java.io.FileReader;
29import java.io.FileWriter;
30import java.io.IOException;
31import java.io.OutputStream;
32import java.io.OutputStreamWriter;
33import java.io.Writer;
34import java.nio.channels.FileChannel;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.sql.Time;
39import java.util.Arrays;
40import java.util.Collection;
41import java.util.HashMap;
42import java.util.LinkedList;
43import java.util.List;
44import java.util.Map;
45import java.util.stream.Collectors;
46
47import org.apollo.io.AudioPathManager;
48import org.expeditee.actions.Actions;
49import org.expeditee.agents.ExistingFramesetException;
50import org.expeditee.agents.InvalidFramesetNameException;
51import org.expeditee.auth.Authenticator;
52import org.expeditee.auth.EncryptedExpReader;
53import org.expeditee.auth.EncryptedExpWriter;
54import org.expeditee.auth.gui.MailBay;
55import org.expeditee.gio.EcosystemManager;
56import org.expeditee.io.Conversion;
57import org.expeditee.io.ExpReader;
58import org.expeditee.io.ExpWriter;
59import org.expeditee.io.FrameReader;
60import org.expeditee.io.FrameWriter;
61import org.expeditee.io.KMSReader;
62import org.expeditee.io.KMSWriter;
63import org.expeditee.items.Item;
64import org.expeditee.items.ItemUtils;
65import org.expeditee.items.Justification;
66import org.expeditee.items.PermissionPair;
67import org.expeditee.items.Text;
68import org.expeditee.items.UserAppliedPermission;
69import org.expeditee.network.FrameShare;
70import org.expeditee.setting.Setting;
71import org.expeditee.settings.UserSettings;
72import org.expeditee.settings.folders.FolderSettings;
73import org.expeditee.settings.templates.TemplateSettings;
74import org.expeditee.stats.Formatter;
75import org.expeditee.stats.Logger;
76import org.expeditee.stats.SessionStats;
77
78/**
79 * This class provides static methods for all saving and loading of Frames
80 * to\from disk. This class also handles any caching of previously loaded
81 * Frames.
82 *
83 * @author jdm18
84 *
85 */
86
87
88public class FrameIO {
89
90 private static final char FRAME_NAME_LAST_CHAR = 'A';
91
92 // The parent path that all others are relative to. Also referred to as Expeditee Home.
93 public static String PARENT_FOLDER;
94
95 // The below paths are categorised based on their use in the new/old regime. See UserSettings.appendDefaultFolders for more inforamtion.
96 // Paths used by Both the new regime (multiuser mode) and the old regime.
97 public static String PROFILE_PATH;
98 public static String FRAME_PATH;
99 public static String MESSAGES_PATH;
100 public static String IMAGES_PATH;
101 public static String AUDIO_PATH;
102 public static String PUBLIC_PATH;
103 public static String TRASH_PATH;
104
105 // Paths used by both the new regime and the old regime, that points to different locations based on regime setting.
106 public static String FONT_PATH;
107 public static String DICT_PATH;
108 public static String EXPORTS_DIR;
109 public static String STATISTICS_DIR;
110 public static String LOGS_DIR;
111
112 // Paths used only by new regime.
113 public static String SHARED_FRAMESETS_PATH;
114 public static String CONTACTS_PATH;
115 public static String RESOURCES_PRIVATE;
116 public static String RESOURCES_PATH;
117 public static String FRAME_PRIVATE_PATH;
118 public static String IMAGES_PRIVATE_PATH;
119 public static String AUDIO_PRIVATE_PATH;
120
121 // Paths used only by old regime.
122 public static String HELP_PATH;
123
124 // Paths that appear to be unused.
125 public static String TEMPLATES_PATH;
126
127 // Variables for controlling cache functionality.
128 public static final int MAX_NAME_LENGTH = 64;
129 public static final int MAX_CACHE = 100;
130 private static HashMap<String, Frame> _Cache = new FrameCache();
131 private static boolean ENABLE_CACHE = true;
132 private static boolean _UseCache = true;
133 private static boolean _SuspendedCache = false;
134
135 private static final String INF_FILENAME = "frame.inf";
136
137 public static void changeParentAndSubFolders(String newFolder) {
138 // Partial Paths.
139 PARENT_FOLDER = newFolder;
140
141 String resourcesPublicPath = PARENT_FOLDER + "resources-public" + File.separator;
142
143 // Standard paths.
144 PUBLIC_PATH = PARENT_FOLDER + "public" + File.separator;
145 MESSAGES_PATH = PARENT_FOLDER + "messages" + File.separator;
146 TRASH_PATH = PARENT_FOLDER + "trash" + File.separator;
147 HELP_PATH = PARENT_FOLDER + "documentation" + File.separator;
148 PROFILE_PATH = PARENT_FOLDER + "profiles" + File.separator;
149 EXPORTS_DIR = PARENT_FOLDER + "exports" + File.separator;
150 STATISTICS_DIR = PARENT_FOLDER + "statistics" + File.separator;
151 LOGS_DIR = PARENT_FOLDER + "logs" + File.separator;
152
153
154 // Conditional paths
155 if (UserSettings.PublicAndPrivateResources) {
156 // Work with a system of public and private folders
157
158 FONT_PATH = resourcesPublicPath + "fonts" + File.separator;
159 DICT_PATH = resourcesPublicPath + "dict" + File.separator;
160 IMAGES_PATH = resourcesPublicPath + "images" + File.separator;
161 AUDIO_PATH = resourcesPublicPath + "audio" + File.separator;
162 FRAME_PATH = resourcesPublicPath + "framesets" + File.separator;
163 } else {
164 FONT_PATH = PARENT_FOLDER + "fonts" + File.separator;
165 DICT_PATH = PARENT_FOLDER + "dict" + File.separator;
166 IMAGES_PATH = PARENT_FOLDER + "images" + File.separator;
167 AUDIO_PATH = PARENT_FOLDER + "audio" + File.separator;
168 FRAME_PATH = PARENT_FOLDER + "framesets" + File.separator;
169 }
170
171 if (!UserSettings.PublicAndPrivateResources || UserSettings.UserName.get().equals(Browser.USER_NOBODY)) {
172
173 if (System.getProperty("user.name").equals(Browser.USER_NOBODY)) {
174 System.err.println("**** FrameIO::changeParentAndSubFolders(): Not setting subfolders for user '"+Browser.USER_NOBODY+"'");
175 }
176
177 // If we are using the old regime, or user.name set to Browser.USER_NOBODY
178 // => then these paths should not be used.
179 RESOURCES_PATH = null;
180 SHARED_FRAMESETS_PATH = null;
181 RESOURCES_PRIVATE = null;
182 FRAME_PRIVATE_PATH = null;
183 IMAGES_PRIVATE_PATH = null;
184 AUDIO_PRIVATE_PATH = null;
185 CONTACTS_PATH = null;
186 } else {
187 String resourcesPrivateIndividualPath = PARENT_FOLDER + "resources-" + UserSettings.UserName.get() + File.separator;
188
189 RESOURCES_PATH = resourcesPublicPath + "documentation" + File.separator;
190 SHARED_FRAMESETS_PATH = resourcesPrivateIndividualPath + "framesets-shared" + File.separator;
191
192 RESOURCES_PRIVATE = PARENT_FOLDER + "resources-private" + File.separator;
193 FRAME_PRIVATE_PATH = resourcesPrivateIndividualPath + "framesets" + File.separator;
194 IMAGES_PRIVATE_PATH = resourcesPrivateIndividualPath + "images" + File.separator;
195 AUDIO_PRIVATE_PATH = resourcesPrivateIndividualPath + "audio" + File.separator;
196 CONTACTS_PATH = resourcesPrivateIndividualPath + "contacts" + File.separator;
197 }
198
199
200 System.err.println("**** FrameIO::changeParentAndSubFolder(): Calling AudioPathManger.changeParentAndSubFolder()");
201 AudioPathManager.changeParentAndSubFolders(newFolder);
202 }
203
204 // All methods are static, this should not be instantiated
205 private FrameIO() {
206 }
207
208 public static boolean isCacheOn() {
209 return _UseCache && ENABLE_CACHE;
210 }
211
212 public static void Precache(String framename) {
213 // if the cache is turned off, do nothing
214 if (!isCacheOn()) {
215 return;
216 }
217
218 // if the frame is already in the cache, do nothing
219 if (_Cache.containsKey(framename.toLowerCase())) {
220 return;
221 }
222
223 // otherwise, load the frame and put it in the cache
224 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Precaching " + framename + ".");
225
226 // do not display errors encountered to the user
227 // (they will be shown at load time)
228 MessageBay.suppressMessages(true);
229 // loading automatically caches the frame is caching is turned on
230 LoadFromDisk(framename, null, false);
231 MessageBay.suppressMessages(false);
232 }
233
234 /**
235 * Checks if a string is a representation of a positive integer.
236 *
237 * @param s
238 * @return true if s is a positive integer
239 */
240 public static boolean isPositiveInteger(String s) {
241 if (s == null || s.length() == 0) {
242 return false;
243 }
244
245 for (int i = 0; i < s.length(); i++) {
246 if (!Character.isDigit(s.charAt(i))) {
247 return false;
248 }
249 }
250 return true;
251 }
252
253 /**
254 * Loads a frame with the specified name.
255 * By using a dot separated framename, users are able to specify the path to find the frameset in.
256 * @param frameName The frame to load.
257 * @return the loaded frame
258 */
259 public static Frame LoadFrame(String frameName) {
260 if (frameName.contains(".")) {
261 String[] split = frameName.split("\\.");
262 String[] pathSplit = Arrays.copyOfRange(split, 0, split.length - 1);
263 String name = split[split.length - 1];
264 String path = Arrays.asList(pathSplit).stream().collect(Collectors.joining(File.separator));
265 return LoadFrame(name, Paths.get(FrameIO.PARENT_FOLDER).resolve(path).toString() + File.separator, false);
266 } else {
267 return LoadFrame(frameName, null, false); //framesets-bryce.home1
268 }
269 }
270
271 public static Frame LoadFrame(String frameName, String path) {
272 return LoadFrame(frameName, path, false);
273 }
274
275 public static Frame LoadFrame(String frameName, String path, boolean ignoreAnnotations) {
276 if (!isValidFrameName(frameName)) {
277 return null;
278 }
279
280 String frameNameLower = frameName.toLowerCase();
281 // first try reading from cache
282 if (isCacheOn() && _Cache.containsKey(frameNameLower)) {
283 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName + " from cache.");
284 Frame frame = _Cache.get(frameNameLower);
285
286 // if frame in cache is older than the one on disk then don't use the cached one
287 File file = new File(frame.getFramePathReal());
288 long lastModified = file.lastModified();
289 if (lastModified <= frame.getLastModifyPrecise()) {
290 return frame;
291 }
292 }
293
294 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Loading " + frameName
295 + " from disk.");
296
297 Frame fromDisk = LoadFromDisk(frameName, path, ignoreAnnotations);
298 return fromDisk;
299 }
300
301 //Loads the 'restore' version of a frame if there is one
302 public static Frame LoadRestoreFrame(Frame frameToRestore) {
303
304 String fullPath = getFrameFullPathName(frameToRestore.getPath(), frameToRestore
305 .getName());
306 //System.out.println("fullpath: " + fullPath);
307 String restoreVersion = fullPath + ".restore";
308 //System.out.println("restoreversion" + restoreVersion);
309 File source = new File(restoreVersion);
310 File dest = new File(fullPath);
311
312 FileChannel inputChannel = null;
313 FileChannel outputChannel = null;
314
315 try{
316 FileInputStream source_fis = new FileInputStream(source);
317 inputChannel = source_fis.getChannel();
318
319 FileOutputStream dest_fos = new FileOutputStream(dest);
320 outputChannel = dest_fos.getChannel();
321
322 outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
323 inputChannel.close();
324 outputChannel.close();
325 source_fis.close();
326 dest_fos.close();
327 }
328 catch(Exception e){
329 System.err.println("No restore point detected.");
330 }
331 String frameName = frameToRestore.getName();
332 String frameNameLower = frameName.toLowerCase();
333
334 // first try reading from cache
335 if (isCacheOn() && _Cache.containsKey(frameNameLower)) {
336 Logger.Log(Logger.SYSTEM, Logger.LOAD, "Clearing " + frameName
337 + " from cache.");
338 _Cache.remove(frameNameLower);
339 }
340
341 return LoadFrame(frameName, frameToRestore.getPath(), true);
342 }
343
344 public static BufferedReader LoadPublicFrame(String frameName) {
345 String fullPath = FrameIO.getFrameFullPathName(PUBLIC_PATH, frameName);
346
347 if (fullPath == null) {
348 return null;
349 }
350
351 File frameFile = new File(fullPath);
352 if (frameFile.exists() && frameFile.canRead()) {
353 try {
354 return new BufferedReader(new FileReader(frameFile));
355 } catch (FileNotFoundException e) {
356 e.printStackTrace();
357 }
358 }
359 return null;
360 }
361
362 private static Frame LoadFromDisk(String framename, String knownPath,
363 boolean ignoreAnnotations) {
364 Frame loaded = null;
365
366 if (knownPath != null) {
367 loaded = LoadKnownPath(knownPath, framename);
368 } else {
369 List<String> directoriesToSearch = FolderSettings.FrameDirs.getAbsoluteDirs();
370
371 if (Authenticator.Authenticated) {
372 // if we are running Expeditee Authenticated, consult user profile as location for framesets first
373 String profilePath = FrameIO.PROFILE_PATH + UserSettings.UserName.get() + File.separator;
374 directoriesToSearch.add(0, profilePath);
375 }
376
377 for (String path : directoriesToSearch) {
378 loaded = LoadKnownPath(path, framename);
379 if (loaded != null) {
380 break;
381 }
382 }
383 }
384
385 if (loaded == null && FrameShare.getInstance() != null) {
386 loaded = FrameShare.getInstance().loadFrame(framename, knownPath);
387 }
388
389 if (loaded != null) {
390 FrameUtils.Parse(loaded, true, ignoreAnnotations);
391 FrameIO.setSavedProperties(loaded);
392 }
393
394 return loaded;
395 }
396
397 /**
398 * Gets a list of all the framesets available to the user
399 *
400 * @return a string containing a list of all the available framesets on
401 * separate lines
402 */
403 public static String getFramesetList() {
404 StringBuffer list = new StringBuffer();
405
406 for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) {
407 File files = new File(path);
408 if (!files.exists()) {
409 continue;
410 }
411 for (File f : (new File(path)).listFiles()) {
412 if (f.isDirectory()) {
413 list.append(f.getName()).append('\n');
414 }
415 }
416 }
417 // remove the final new line char
418 list.deleteCharAt(list.length() - 1);
419 return list.toString();
420 }
421
422 /**
423 * Gets the full path and file name of the frame.
424 * This is a alias for Frame::getFramePathLogical
425 * @param path-
426 * the directory in which to look for the frameset containing the
427 * frame.
428 * @param frameName-
429 * the name of the frame for which the path is being requested.
430 * @return null if the frame can not be located.
431 */
432 public static synchronized String getFrameFullPathName(String path,
433 String frameName) {
434
435 String source;
436 String fileName = null;
437 if(frameName.contains("restore")){
438 source = path + File.separator;// + frameName;
439 fileName = path + File.separator + frameName + ExpReader.EXTENTION;
440
441 }
442 else
443 {
444 source = path + Conversion.getFramesetName(frameName)
445 + File.separator;
446 }
447
448
449 File tester = new File(source);
450 if (!tester.exists()) {
451 return null;
452 }
453
454 String fullPath;
455
456 if(frameName.contains("restore")){
457
458 fullPath = fileName;
459 }
460 else
461 {
462 // check for the new file name format
463 fullPath = source + Conversion.getFrameNumber(frameName)
464 + ExpReader.EXTENTION;
465 }
466
467 tester = new File(fullPath);
468
469 if (tester.exists()) {
470 return fullPath;
471 }
472
473 // check for oldfile name format
474 fullPath = source + Conversion.getFramesetName(frameName) + "."
475 + Conversion.getFrameNumber(frameName);
476 tester = new File(fullPath);
477
478 if (tester.exists()) {
479 return fullPath;
480 }
481
482 return null;
483 }
484
485 public static boolean canAccessFrame(String frameName) {
486 Frame current = DisplayController.getCurrentFrame();
487 // Just in case the current frame is not yet saved...
488 if (frameName.equals(current.getName())) {
489 FrameIO.SaveFrame(current, false, false);
490 current.change();
491 return true;
492 }
493
494 for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) {
495 if (getFrameFullPathName(path, frameName) != null) {
496 return true;
497 }
498 }
499 return false;
500 }
501
502 public static Collection<String> searchFrame(String frameName,
503 String pattern, String path) {
504 String fullPath = null;
505 if (path == null) {
506 for (String possiblePath : FolderSettings.FrameDirs.getAbsoluteDirs()) {
507 fullPath = getFrameFullPathName(possiblePath, frameName);
508 if (fullPath != null) {
509 break;
510 }
511 }
512 } else {
513 fullPath = getFrameFullPathName(path, frameName);
514 }
515 // If the frame was not located return null
516 if (fullPath == null) {
517 return null;
518 }
519 Collection<String> results = new LinkedList<String>();
520 // Open the file and search the text items
521 try {
522 BufferedReader reader = new BufferedReader(new FileReader(fullPath));
523 String next;
524 while (reader.ready() && ((next = reader.readLine()) != null)) {
525 if (next.startsWith("T")) {
526 String toSearch = next.substring(2);
527 if (toSearch.toLowerCase().contains(pattern)) {
528 results.add(toSearch);
529 }
530 }
531 }
532 reader.close();
533 } catch (FileNotFoundException e) {
534 e.printStackTrace();
535 return null;
536 } catch (IOException e) {
537 e.printStackTrace();
538 }
539 return results;
540 }
541
542 private static Frame LoadKnownPath(String path, String frameName) {
543 String fullPath = getFrameFullPathName(path, frameName);
544 if (fullPath == null) {
545 return null;
546 }
547
548 try {
549 FrameReader reader;
550
551 // Get the frameset name.
552 int i = frameName.length() - 1;
553 for (; i >= 0; i--) {
554 if (!Character.isDigit(frameName.charAt(i))) {
555 break;
556 }
557 }
558 if (i < 0) {
559 System.err.println("LoadKnownFrame was provided with a invalid Frame name: " + frameName);
560 return null;
561 }
562 String framesetName = frameName.substring(0, i + 1);
563
564 String redirectTo = ExpReader.redirectTo(fullPath);
565 while (redirectTo != null) {
566 fullPath = path + framesetName + File.separator + redirectTo;
567 redirectTo = ExpReader.redirectTo(fullPath);
568 }
569
570 if (fullPath.endsWith(ExpReader.EXTENTION)) {
571 if (EncryptedExpReader.isEncryptedExpediteeFile(fullPath)) {
572 reader = new EncryptedExpReader(frameName);
573 } else {
574 reader = new ExpReader(frameName);
575 }
576 } else {
577 reader = new KMSReader();
578 }
579 Frame frame = reader.readFrame(fullPath);
580
581 if (frame == null) {
582 MessageBay.errorMessage("Error: " + frameName
583 + " could not be successfully loaded.");
584 return null;
585 }
586
587 frame.setPath(path);
588
589 // do not put 0 frames or virtual frames into the cache
590 // Why are zero frames not put in the cache
591 if (_Cache.size() > MAX_CACHE) {
592 _Cache.clear();
593 }
594
595 if (frame.getNumber() > 0 && isCacheOn()) {
596 _Cache.put(frameName.toLowerCase(), frame);
597 }
598
599 return frame;
600 } catch (IOException ioe) {
601 ioe.printStackTrace();
602 Logger.Log(ioe);
603 } catch (Exception e) {
604 e.printStackTrace();
605 Logger.Log(e);
606 MessageBay.errorMessage("Error: " + frameName
607 + " could not be successfully loaded.");
608 }
609
610 return null;
611 }
612
613 public static void Reload() {
614 // disable cache
615 boolean cache = _UseCache;
616
617 _UseCache = false;
618 Frame fresh = FrameIO.LoadFrame(DisplayController.getCurrentFrame().getName());
619 _UseCache = cache;
620 if (_Cache.containsKey(fresh.getName().toLowerCase())) {
621 addToCache(fresh);
622 }
623 DisplayController.setCurrentFrame(fresh, false);
624 }
625
626 public static Frame LoadPrevious(Frame current) {
627 checkTDFC(current);
628
629 // the current name and number
630 String name = current.getFramesetName();
631 int num = current.getNumber() - 1;
632
633 // loop until a frame that exists is found
634 for (; num >= 0; num--) {
635 Frame f = LoadFrame(name + num, current.getPath());
636 if (f != null) {
637 return f;
638 }
639 }
640
641 // if we did not find another Frame then this one must be the last one
642 // in the frameset
643 MessageBay
644 .displayMessageOnce("This is the first frame in the frameset");
645 return null;
646 }
647
648 /**
649 * Returns the next Frame in the current Frameset (The Frame with the next
650 * highest Frame number) If the current Frame is the last one in the
651 * Frameset, or an error occurs then null is returned.
652 *
653 * @return The Frame after this one in the current frameset, or null
654 */
655 public static Frame LoadNext(Frame current) {
656 checkTDFC(current);
657
658 // the current name and number
659 int num = current.getNumber() + 1;
660 int max = num + 1;
661 String name = current.getFramesetName();
662
663 // read the maximum from the INF file
664 try {
665 max = ReadINF(current.getPath(), current.getFramesetName(), false);
666 } catch (IOException ioe) {
667 MessageBay.errorMessage("Error loading INF file for frameset '"
668 + name + "'");
669 return null;
670 }
671
672 // loop until a frame that exists is found
673 for (; num <= max; num++) {
674 Frame f = LoadFrame(name + num, current.getPath());
675 if (f != null) {
676 return f;
677 }
678 }
679
680 // if we did not find another Frame then this one must be the last one
681 // in the frameset
682 MessageBay.displayMessageOnce("This is the last frame in the frameset");
683 return null;
684 }
685
686 /**
687 * This method checks if the current frame has just been created with TDFC.
688 * If it has the frame is saved regardless of whether it has been edited or
689 * not and the TDFC item property is cleared. This is to ensure that the
690 * link is saved on the parent frame.
691 *
692 * @param current
693 */
694 public static void checkTDFC(Frame current) {
695 if (FrameUtils.getTdfcItem() != null) {
696 FrameUtils.setTdfcItem(null);
697 current.change();
698 }
699 }
700
701 public static Frame LoadLast(String framesetName, String path) {
702 // read the maximum from the INF file
703 int max;
704 try {
705 max = ReadINF(path, framesetName, false);
706 } catch (IOException ioe) {
707 MessageBay.errorMessage("Error loading INF file for frameset '"
708 + framesetName + "'");
709 return null;
710 }
711
712 // loop backwards until a frame that exists is found
713 for (int num = max; num > 0; num--) {
714 Frame f = LoadFromDisk(framesetName + num, path, false);
715 if (f != null) {
716 return f;
717 }
718 }
719
720 // if we did not find another Frame then this one must be the last one
721 // in the frameset
722 MessageBay.displayMessage("This is the last frame in the frameset");
723 return null;
724 }
725
726 public static Frame LoadZero(String framesetName, String path) {
727 return LoadFrame(framesetName + 0);
728 }
729
730 public static Frame LoadZero() {
731 Frame current = DisplayController.getCurrentFrame();
732 return LoadZero(current.getFramesetName(), current.getPath());
733 }
734
735 public static Frame LoadLast() {
736 Frame current = DisplayController.getCurrentFrame();
737 return LoadLast(current.getFramesetName(), current.getPath());
738 }
739
740 public static Frame LoadNext() {
741 return LoadNext(DisplayController.getCurrentFrame());
742 }
743
744 public static Frame LoadPrevious() {
745 return LoadPrevious(DisplayController.getCurrentFrame());
746 }
747
748 /**
749 * Deletes the given Frame on disk and removes the cached Frame if there is
750 * one. Also adds the deleted frame into the deletedFrames frameset.
751 *
752 * @param toDelete
753 * The Frame to be deleted
754 * @return The name the deleted frame was changed to, or null if the delete
755 * failed
756 */
757 public static String DeleteFrame(Frame toDelete) throws IOException,
758 SecurityException {
759 if (toDelete == null) {
760 return null;
761 }
762
763 // Dont delete the zero frame
764 if (toDelete.getNumber() == 0) {
765 throw new SecurityException("Deleting a zero frame is illegal");
766 }
767
768 // Dont delete the zero frame
769 if (!toDelete.isLocal()) {
770 throw new SecurityException("Attempted to delete remote frame");
771 }
772
773 SaveFrame(toDelete);
774
775 // Copy deleted frames to the DeletedFrames frameset
776 // get the last used frame in the destination frameset
777 final String DELETED_FRAMES = "DeletedFrames";
778 int lastNumber = FrameIO.getLastNumber(DELETED_FRAMES);
779 String framePath;
780 try {
781 // create the new frameset
782 Frame one = FrameIO.CreateFrameset(DELETED_FRAMES, toDelete
783 .getPath());
784 framePath = one.getPath();
785 lastNumber = 0;
786 } catch (Exception e) {
787 Frame zero = FrameIO.LoadFrame(DELETED_FRAMES + "0");
788 framePath = zero.getPath();
789 }
790
791 // get the fill path to determine which file version it is
792 String source = getFrameFullPathName(toDelete.getPath(), toDelete
793 .getName());
794
795 String oldFrameName = toDelete.getName().toLowerCase();
796 // Now save the frame in the new location
797 toDelete.setFrameset(DELETED_FRAMES);
798 toDelete.setFrameNumber(lastNumber + 1);
799 toDelete.setPath(framePath);
800 ForceSaveFrame(toDelete);
801
802 if (_Cache.containsKey(oldFrameName)) {
803 _Cache.remove(oldFrameName);
804 }
805
806 File del = new File(source);
807
808 java.io.FileInputStream ff = new java.io.FileInputStream(del);
809 ff.close();
810
811 if (del.delete()) {
812 return toDelete.getName();
813 }
814
815 return null;
816 }
817
818 /**
819 * Creates a new Frame in the given frameset and assigns it the given Title,
820 * which can be null. The newly created Frame is a copy of the frameset's .0
821 * file with the number updated based on the last recorded Frame name in the
822 * frameset's INF file.
823 *
824 * @param frameset
825 * The frameset to create the new Frame in
826 * @param frameTitle
827 * The title to assign to the newly created Frame (can be NULL).
828 * @return The newly created Frame.
829 */
830 public static synchronized Frame CreateFrame(String frameset,
831 String frameTitle, String templateFrame) throws RuntimeException {
832
833 if (!FrameIO.isValidFramesetName(frameset)) {
834 throw new RuntimeException(frameset
835 + " is not a valid frameset name");
836 }
837
838 int next = -1;
839
840 // disable caching of 0 frames
841 // Mike says: Why is caching of 0 frames being disabled?
842 /*
843 * Especially since 0 frames are not event put into the cache in the
844 * frist place
845 */
846 // SuspendCache();
847 /*
848 * Suspending the cache causes infinate loops when trying to load a zero
849 * frame which has a ao which contains an v or av which contains a link
850 * to the ao frame
851 */
852
853 String zeroFrameName = frameset + "0";
854 Frame destFramesetZero = LoadFrame(zeroFrameName);
855 if (destFramesetZero == null) {
856 throw new RuntimeException(zeroFrameName + " could not be found");
857 }
858
859 Frame template = null;
860 if (templateFrame == null) {
861 // load in frame.0
862 template = destFramesetZero;
863 } else {
864 template = LoadFrame(templateFrame);
865 if (template == null) {
866 throw new RuntimeException("LinkTemplate " + templateFrame
867 + " could not be found");
868 }
869 }
870
871 ResumeCache();
872
873 // read the next number from the INF file
874 try {
875 next = ReadINF(destFramesetZero.getPath(), frameset, true);
876 } catch (IOException ioe) {
877 ioe.printStackTrace();
878 throw new RuntimeException("INF file could not be read");
879 }
880
881 // Remove the old frame from the cache then add the new one
882 // TODO figure out some way that we can put both in the cache
883 _Cache.remove(template.getName().toLowerCase());
884 // set the number and title of the new frame
885 template.setName(frameset, ++next);
886 template.setTitle(frameTitle);
887 // _Cache.put(template.getName().toLowerCase(), template);
888
889 Logger.Log(Logger.SYSTEM, Logger.TDFC, "Creating new frame: "
890 + template.getName() + " from TDFC");
891
892 template.setOwner(UserSettings.UserName.get());
893 template.reset();
894 template.resetDateCreated();
895
896 for (Item i : template.getItems()) {
897 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT)) {
898 i.setLink(null);
899 }
900 }
901
902 // do auto shrinking of the title IF not in twin frames mode and the title is not centred
903 Item titleItem = template.getTitleItem();
904
905 if (!DisplayController.isTwinFramesOn() && !Justification.center.equals(((Text)titleItem).getJustification())) {
906 if ((titleItem.getX() + 1) < template.getNameItem().getX()) {
907 int title_item_xr = titleItem.getX() + titleItem.getBoundsWidth(); // should really be '... -1'
908 int frame_name_xl = template.getNameItem().getX();
909 if (frame_name_xl < DisplayController.MINIMUM_FRAME_WIDTH) {
910 frame_name_xl = DisplayController.MINIMUM_FRAME_WIDTH;
911 }
912
913 while ((titleItem.getSize() > Text.MINIMUM_FONT_SIZE) && title_item_xr > frame_name_xl) {
914 titleItem.setSize(titleItem.getSize() - 1);
915 System.err.println("**** shrunk titleItem: " + titleItem + " to font size: " + titleItem.getSize());
916 }
917 } else {
918 System.out.println("Bad title x position: " + titleItem.getX());
919 }
920 }
921 // Assign a width to the title.
922 titleItem.setRightMargin(template.getNameItem().getX(), true);
923
924 return template;
925 }
926
927 public static void DisableCache() {
928 _UseCache = false;
929 }
930
931 public static void EnableCache() {
932 _UseCache = true;
933 }
934
935 public static void SuspendCache() {
936 if (_UseCache) {
937 DisableCache();
938 _SuspendedCache = true;
939 } else {
940 _SuspendedCache = false;
941 }
942 }
943
944 public static void ResumeCache() {
945 if (_SuspendedCache) {
946 EnableCache();
947 _SuspendedCache = false;
948 }
949 }
950
951 public static void RefreshCacheImages()
952 {
953 SuspendCache();
954 for (Frame frame : _Cache.values()) {
955 frame.setBuffer(null);
956 }
957 ResumeCache();
958 }
959
960 /**
961 * Creates a new frameset using the given name. This includes creating a new
962 * subdirectory in the <code>FRAME_PATH</code> directory, Copying over the
963 * default.0 frame from the default frameset, copying the .0 Frame to make a
964 * .1 Frame, and creating the frameset's INF file.
965 *
966 * @param frameset
967 * The name of the Frameset to create
968 * @return The first Frame of the new Frameset (Frame.1)
969 */
970 public static Frame CreateFrameset(String frameset, String path)
971 throws Exception {
972 return CreateFrameset(frameset, path, false);
973 }
974
975 /**
976 * Tests if the given String is a 'proper' framename, that is, the String
977 * must begin with a character, end with a number with 0 or more letters and
978 * numbers in between.
979 *
980 * @param frameName
981 * The String to test for validity as a frame name
982 * @return True if the given framename is proper, false otherwise.
983 */
984 public static boolean isValidFrameName(String frameName) {
985
986 if (frameName == null || frameName.length() < 2) {
987 return false;
988 }
989
990 int lastCharIndex = frameName.length() - 1;
991 // String must begin with a letter and end with a digit
992 if (!Character.isLetter(frameName.charAt(0))
993 || !Character.isDigit(frameName.charAt(lastCharIndex))) {
994 return false;
995 }
996
997 // All the characters between first and last must be letters
998 // or digits
999 for (int i = 1; i < lastCharIndex; i++) {
1000 if (!isValidFrameNameChar(frameName.charAt(i))) {
1001 return false;
1002 }
1003 }
1004 return true;
1005 }
1006
1007 private static boolean isValidFrameNameChar(char c) {
1008 return c == '-' || c == '.' || Character.isLetterOrDigit(c);
1009 }
1010
1011 /**
1012 * Saves the given Frame to disk in the corresponding frameset directory.
1013 * This is the same as calling SaveFrame(toSave, true)
1014 *
1015 * @param toSave
1016 * The Frame to save to disk
1017 */
1018 public static String SaveFrame(Frame toSave) {
1019 return SaveFrame(toSave, true);
1020 }
1021
1022 /**
1023 * Saves a frame.
1024 *
1025 * @param toSave
1026 * the frame to save
1027 * @param inc
1028 * true if the frames counter should be incremented
1029 * @return the text content of the frame
1030 */
1031 public static String SaveFrame(Frame toSave, boolean inc) {
1032 return SaveFrame(toSave, inc, true);
1033 }
1034
1035 /**
1036 * Saves the given Frame to disk in the corresponding frameset directory, if
1037 * inc is true then the saved frames counter is incremented, otherwise it is
1038 * untouched.
1039 *
1040 * @param toSave
1041 * The Frame to save to disk
1042 * @param inc
1043 * True if the saved frames counter should be incremented, false otherwise.
1044 * @param checkBackup
1045 * True if the frame should be checked for the back up tag
1046 */
1047 public static String SaveFrame(Frame toSave, boolean inc, boolean checkBackup) {
1048 // TODO When loading a frame maybe append onto the event history too-
1049 // with a break to indicate the end of a session
1050
1051 if (toSave == null || !toSave.hasChanged() || toSave.isSaved()) {
1052 return "";
1053 }
1054
1055 // Dont save if the frame is protected and it exists
1056 if (checkBackup && toSave.isReadOnly()) {
1057 _Cache.remove(toSave.getName().toLowerCase());
1058 return "";
1059 }
1060
1061 /* Dont save the frame if it has the noSave tag */
1062 if (toSave.hasAnnotation("nosave")) {
1063 Actions.LegacyPerformActionCatchErrors(toSave, null, "Restore");
1064 return "";
1065 }
1066
1067 // Save frame that is not local through the Networking classes
1068 if (!toSave.isLocal()) {
1069 return FrameShare.getInstance().saveFrame(toSave);
1070 }
1071
1072 /* Format the frame if it has the autoFormat tag */
1073 if (toSave.hasAnnotation("autoformat")) {
1074 Actions.LegacyPerformActionCatchErrors(toSave, null, "Format");
1075 }
1076
1077 /**
1078 * Get the full path only to determine which format to use for saving
1079 * the frame. At this stage use Exp format for saving Exp frames only.
1080 * Later this will be changed so that KMS frames will be updated to the
1081 * Exp format.
1082 */
1083 String fullPath = getFrameFullPathName(toSave.getPath(), toSave
1084 .getName());
1085
1086 // Check if the frame exists
1087 if (checkBackup && fullPath == null) {
1088 // The first time a frame with the backup tag is saved, dont back it
1089 // up
1090 checkBackup = false;
1091 }
1092
1093 FrameWriter writer = null;
1094 int savedVersion;
1095 try {
1096 // if its a new frame or an existing Exp frame...
1097 if (fullPath == null || fullPath.endsWith(ExpReader.EXTENTION)) {
1098 if (toSave.getNumber() != Authenticator.CREDENTIALS_FRAME &&
1099 toSave.getEncryptionLabel() != null) {
1100 writer = new EncryptedExpWriter(toSave.getEncryptionLabel());
1101 savedVersion = EncryptedExpReader.getVersion(fullPath);
1102 } else {
1103 writer = new ExpWriter();
1104 savedVersion = ExpReader.getVersion(fullPath);
1105 }
1106
1107 // Is the file this would be saved to a redirect?
1108 String redirectTo = ExpReader.redirectTo(fullPath);
1109 if (redirectTo != null) {
1110 String redirectedPath = toSave.getFramePathReal();
1111 writer.setOutputLocation(redirectedPath);
1112 }
1113
1114 } else {
1115 writer = new KMSWriter();
1116 savedVersion = KMSReader.getVersion(fullPath);
1117 }
1118
1119 // Check if the frame doesnt exist
1120 // if (savedVersion < 0) {
1121 // /*
1122 // * This will happen if the user has two Expeditee's running at
1123 // * once and closes the first. When the second one closes the
1124 // * messages directory will have been deleted.
1125 // */
1126 // MessageBay
1127 // .errorMessage("Could not save frame that does not exist: "
1128 // + toSave.getName());
1129 // return null;
1130 // }
1131
1132 // Check if we are trying to save an out of date version
1133 String framesetName = toSave.getFramesetName();
1134 boolean isBayFrameset =
1135 framesetName.equalsIgnoreCase(MessageBay.MESSAGES_FRAMESET_NAME) ||
1136 framesetName.equalsIgnoreCase(MailBay.EXPEDITEE_MAIL_FRAMESET_NAME);
1137 long frameLastModify = toSave.getLastModifyPrecise();
1138 long fileLastModify = new File(toSave.getFramePathReal()).lastModified();
1139 boolean fileModifyConflict = fileLastModify > frameLastModify && !isBayFrameset;
1140 boolean versionConflict = savedVersion > toSave.getVersion() && !isBayFrameset;
1141 if (fileModifyConflict || versionConflict) {
1142 // remove this frame from the cache if it is there
1143 // This will make sure links to the original are set correctly
1144 _Cache.remove(toSave.getName().toLowerCase());
1145 int nextnum = ReadINF(toSave.getPath(), toSave
1146 .getFramesetName(), false) + 1;
1147 SuspendCache();
1148 Frame original = LoadFrame(toSave.getName());
1149 toSave.setFrameNumber(nextnum);
1150 ResumeCache();
1151 // Put the modified version in the cache
1152 addToCache(toSave);
1153 // Show the messages alerting the user
1154 Text originalMessage = new Text(-1);
1155 originalMessage.setColor(MessageBay.ERROR_COLOR);
1156 StringBuilder message = new StringBuilder(original.getName()
1157 + " was updated by another user. ");
1158 if (fileModifyConflict) {
1159 message.append("{ File modify conflict }");
1160 }
1161 if (versionConflict) {
1162 message.append("{ Version conflict }");
1163 }
1164 originalMessage.setText(message.toString());
1165 originalMessage.setLink(original.getName());
1166 Text yourMessage = new Text(-1);
1167 yourMessage.setColor(MessageBay.ERROR_COLOR);
1168 yourMessage.setText("Your version was renamed "
1169 + toSave.getName());
1170 yourMessage.setLink(toSave.getName());
1171 MessageBay.displayMessage(originalMessage);
1172 MessageBay.displayMessage(yourMessage);
1173 EcosystemManager.getMiscManager().beep();
1174 } else if (checkBackup
1175 && ItemUtils.ContainsExactTag(toSave.getItems(),
1176 ItemUtils.TAG_BACKUP)) {
1177 SuspendCache();
1178 String oldFramesetName = toSave.getFramesetName() + "-old";
1179
1180 Frame original = LoadFrame(toSave.getName());
1181 if (original == null) {
1182 original = toSave;
1183 }
1184 int orignum = original.getNumber();
1185
1186 int nextnum = -1;
1187 try {
1188 nextnum = ReadINF(toSave.getPath(), oldFramesetName, false) + 1;
1189 } catch (RuntimeException e) {
1190 try {
1191 CreateFrameset(oldFramesetName, toSave.getPath());
1192 nextnum = 1;
1193 } catch (Exception e1) {
1194 e1.printStackTrace();
1195 }
1196 // e.printStackTrace();
1197 }
1198
1199 if (nextnum > 0) {
1200 original.setFrameset(oldFramesetName);
1201 original.setFrameNumber(nextnum);
1202 original.setPermission(new PermissionPair(UserAppliedPermission.copy));
1203 original.change();
1204 SaveFrame(original, false, false);
1205 }
1206
1207 Item i = ItemUtils.FindExactTag(toSave.getItems(),
1208 ItemUtils.TAG_BACKUP);
1209 i.setLink(original.getName());
1210 toSave.setFrameNumber(orignum);
1211 ResumeCache();
1212 }
1213
1214 // int oldMode = FrameGraphics.getMode();
1215 // if (oldMode != FrameGraphics.MODE_XRAY)
1216 // FrameGraphics.setMode(FrameGraphics.MODE_XRAY, true);
1217
1218 writer.writeFrame(toSave);
1219 // FrameGraphics.setMode(oldMode, true);
1220 toSave.setSaved();
1221
1222 // Update general stuff about frame
1223 setSavedProperties(toSave);
1224
1225 if (inc) {
1226 SessionStats.SavedFrame(toSave.getName());
1227 }
1228
1229 // avoid out-of-sync frames (when in TwinFrames mode)
1230 if (_Cache.containsKey(toSave.getName().toLowerCase())) {
1231 addToCache(toSave);
1232 }
1233
1234 Logger.Log(Logger.SYSTEM, Logger.SAVE, "Saving " + toSave.getName()
1235 + " to disk.");
1236
1237 // check that the INF file is not out of date
1238 int last = ReadINF(toSave.getPath(), toSave.getFramesetName(),
1239 false);
1240 if (last <= toSave.getNumber()) {
1241 WriteINF(toSave.getPath(), toSave.getFramesetName(), toSave
1242 .getName());
1243 }
1244
1245 // check if this was the profile frame (and thus needs
1246 // re-parsing)
1247 if (isProfileFrame(toSave)) {
1248 Frame profile = FrameIO.LoadFrame(toSave.getFramesetName() + "1");
1249 assert (profile != null);
1250 FrameUtils.ParseProfile(profile);
1251 }
1252 } catch (IOException ioe) {
1253 ioe.printStackTrace();
1254 ioe.getStackTrace();
1255 Logger.Log(ioe);
1256 return null;
1257 }
1258 toSave.notifyObservers(false);
1259
1260 return writer.getFileContents();
1261 }
1262
1263 /**
1264 * Saves the given Frame to disk in the corresponding frameset directory as a RESTORE, if
1265 * inc is true then the saved frames counter is incremented, otherwise it is
1266 * untouched.
1267 *
1268 * @param toSave
1269 * The Frame to save to disk as the DEFAULT COPY
1270 * @param inc
1271 * True if the saved frames counter should be incremented, false
1272 * otherwise.
1273 * @param checkBackup
1274 * True if the frame should be checked for the back up tag
1275 */
1276 public static String SaveFrameAsRestore(Frame toSave, boolean inc,
1277 boolean checkBackup) {
1278
1279 String sf = SaveFrame(toSave, inc, checkBackup);
1280 String fullPath = getFrameFullPathName(toSave.getPath(), toSave
1281 .getName());
1282 //System.out.println(fullPath);
1283 String restoreVersion = fullPath + ".restore";
1284 File source = new File(fullPath);
1285 File dest = new File(restoreVersion);
1286
1287 FileChannel inputChannel = null;
1288 FileChannel outputChannel = null;
1289
1290 try{
1291 FileInputStream source_fis = new FileInputStream(source);
1292 inputChannel = source_fis.getChannel();
1293
1294 FileOutputStream dest_fos = new FileOutputStream(dest);
1295 outputChannel = dest_fos.getChannel();
1296
1297 outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
1298 inputChannel.close();
1299 outputChannel.close();
1300 source_fis.close();
1301 dest_fos.close();
1302 }
1303 catch(Exception e){
1304 e.printStackTrace();
1305 }
1306
1307 return sf;
1308 }
1309
1310 /**
1311 * @param toAdd
1312 */
1313 public static void addToCache(Frame toAdd) {
1314 _Cache.put(toAdd.getName().toLowerCase(), toAdd);
1315 }
1316
1317 public static void ClearCache() {
1318 _Cache.clear();
1319 }
1320
1321 /**
1322 * Checks if a frame is in the current user profile frameset.
1323 *
1324 * @param toCheck
1325 * the frame to check
1326 * @return true if the frame is in the current user profile frameset
1327 */
1328 public static boolean isProfileFrame(Frame toCheck)
1329 {
1330 if (toCheck.getNumber() == 0) {
1331 return false;
1332 }
1333
1334 return toCheck.getPath().equals(PROFILE_PATH);
1335 // return toCheck.getFramesetName()
1336 // .equalsIgnoreCase(UserSettings.ProfileName);
1337 }
1338
1339 public static Frame LoadProfile(String userName)
1340 {
1341 final String profilesLoc = System.getProperty("profiles.loc");
1342 if (profilesLoc != null) {
1343 return LoadFrame(userName + "1", profilesLoc);
1344 } else {
1345 return LoadFrame(userName + "1");
1346 }
1347 }
1348
1349 public static Frame CreateNewProfile(String username, Map<String, Setting> initialSettings) throws InvalidFramesetNameException, ExistingFramesetException {
1350 Frame profile = CreateFrameset(username, PROFILE_PATH, true);
1351 FrameUtils.CreateDefaultProfile(username, profile, initialSettings);
1352 return profile;
1353 }
1354
1355 /**
1356 * Reads the INF file that corresponds to the given Frame name
1357 *
1358 * @param framename
1359 * The Frame to lookup the INF file for
1360 * @throws IOException
1361 * Any exceptions encountered by the BufferedReader used to read
1362 * the INF.
1363 */
1364 public static int ReadINF(String path, String frameset, boolean update)
1365 throws IOException {
1366 assert (!frameset.endsWith("."));
1367 try {
1368 // read INF
1369 BufferedReader reader;
1370 try {
1371 // Check on the local drive
1372 reader = new BufferedReader(new FileReader(path
1373 + frameset.toLowerCase() + File.separator
1374 + INF_FILENAME));
1375 } catch (Exception e) {
1376 reader = new BufferedReader(new FileReader(path
1377 + frameset.toLowerCase() + File.separator
1378 + frameset.toLowerCase() + ".inf"));
1379 }
1380 String inf = reader.readLine();
1381 reader.close();
1382
1383 int next = Conversion.getFrameNumber(inf);
1384 // update INF file
1385 if (update) {
1386 try {
1387 WriteINF(path, frameset, frameset + (next + 1));
1388 } catch (IOException ioe) {
1389 ioe.printStackTrace();
1390 Logger.Log(ioe);
1391 }
1392 }
1393 return next;
1394 } catch (Exception e) {
1395 }
1396
1397 // Check peers
1398 return FrameShare.getInstance().getInfNumber(path, frameset, update);
1399 }
1400
1401 /**
1402 * Writes the given String out to the INF file corresponding to the current
1403 * frameset.
1404 *
1405 * @param toWrite
1406 * The String to write to the file.
1407 * @throws IOException
1408 * Any exception encountered by the BufferedWriter.
1409 */
1410 public static void WriteINF(String path, String frameset, String frameName)
1411 throws IOException {
1412 try {
1413 assert (!frameset.endsWith("."));
1414
1415 path += frameset.toLowerCase() + File.separator + INF_FILENAME;
1416
1417 BufferedWriter writer = new BufferedWriter(new FileWriter(path));
1418 writer.write(frameName);
1419 writer.close();
1420 } catch (Exception e) {
1421
1422 }
1423 }
1424
1425 public static boolean FrameIsCached(String name) {
1426 return _Cache.containsKey(name);
1427 }
1428
1429 /**
1430 * Gets a frame from the cache.
1431 *
1432 * @param name
1433 * The frame to get from the cache
1434 *
1435 * @return The frame from cache. Null if not cached.
1436 */
1437 public static Frame FrameFromCache(String name) {
1438 return _Cache.get(name);
1439 }
1440
1441 public static String ConvertToValidFramesetName(String toValidate) {
1442 assert (toValidate != null && toValidate.length() > 0);
1443
1444 StringBuffer result = new StringBuffer();
1445
1446 if (Character.isDigit(toValidate.charAt(0))) {
1447 result.append(FRAME_NAME_LAST_CHAR);
1448 }
1449
1450 boolean capital = false;
1451 for (int i = 0; i < toValidate.length()
1452 && result.length() < MAX_NAME_LENGTH; i++) {
1453 char cur = toValidate.charAt(i);
1454
1455 // capitalize all characters after spaces
1456 if (Character.isLetterOrDigit(cur)) {
1457 if (capital) {
1458 capital = false;
1459 result.append(Character.toUpperCase(cur));
1460 } else {
1461 result.append(cur);
1462 }
1463 } else {
1464 capital = true;
1465 }
1466 }
1467 assert (result.length() > 0);
1468 int lastCharIndex = result.length() - 1;
1469 if (!Character.isLetter(result.charAt(lastCharIndex))) {
1470 if (lastCharIndex == MAX_NAME_LENGTH - 1) {
1471 result.setCharAt(lastCharIndex, FRAME_NAME_LAST_CHAR);
1472 } else {
1473 result.append(FRAME_NAME_LAST_CHAR);
1474 }
1475 }
1476
1477 assert (isValidFramesetName(result.toString()));
1478 return result.toString();
1479 }
1480
1481 public static Frame CreateNewFrame(Item linker) throws RuntimeException {
1482 String title = linker.getName();
1483
1484 String templateLink = linker.getAbsoluteLinkTemplate();
1485 String framesetLink = linker.getAbsoluteLinkFrameset();
1486 String frameset = (framesetLink != null ? framesetLink : DisplayController
1487 .getCurrentFrame().getFramesetName());
1488
1489 Frame newFrame = FrameIO.CreateFrame(frameset, title, templateLink);
1490 return newFrame;
1491 }
1492
1493 public static Frame CreateNewFrame(Item linker, OnNewFrameAction action) throws RuntimeException {
1494 Frame newFrame = FrameIO.CreateNewFrame(linker);
1495 if(action != null) {
1496 action.exec(linker, newFrame);
1497 }
1498 return newFrame;
1499 }
1500
1501 /**
1502 * Creates a new Frameset on disk, including a .0, .1, and .inf files. The
1503 * Default.0 frame is copied to make the initial .0 and .1 Frames
1504 *
1505 * @param name
1506 * The Frameset name to use
1507 * @return The name of the first Frame in the newly created Frameset (the .1
1508 * frame)
1509 */
1510 public static Frame CreateNewFrameset(String name) throws Exception {
1511 String path = DisplayController.getCurrentFrame().getPath();
1512
1513 // if current frameset is profile directory change it to framesets
1514 if (path.equals(FrameIO.PROFILE_PATH)) {
1515 path = FrameIO.FRAME_PATH;
1516 }
1517
1518 Frame newFrame = FrameIO.CreateFrameset(name, path);
1519
1520 if (newFrame == null) {
1521 // Cant create directories if the path is readonly or there is no
1522 // space available
1523 newFrame = FrameIO.CreateFrameset(name, FrameIO.FRAME_PATH);
1524 }
1525
1526 if (newFrame == null) {
1527 // TODO handle running out of disk space here
1528 }
1529
1530 return newFrame;
1531 }
1532
1533 /**
1534 *
1535 * @param frameset
1536 * @return
1537 */
1538 public static int getLastNumber(String frameset) { // Rob thinks it might
1539 // have been
1540 // GetHighestNumExFrame
1541 // TODO minimise the number of frames being read in!!
1542 int num = -1;
1543
1544 Frame zero = LoadFrame(frameset + "0");
1545
1546 // the frameset does not exist (or has no 0 frame)
1547 if (zero == null) {
1548 return -1;
1549 }
1550
1551 try {
1552 num = ReadINF(zero.getPath(), frameset, false);
1553 } catch (IOException e) {
1554 // TODO Auto-generated catch block
1555 // e.printStackTrace();
1556 }
1557
1558 /*
1559 * Michael doesnt think the code below is really needed... it will just
1560 * slow things down when we are reading frames over a network***** for (;
1561 * num >= 0; num--) { System.out.println("This code is loading frames to
1562 * find the highest existing frame..."); if (LoadFrame(frameset + num) !=
1563 * null) break; }
1564 */
1565
1566 return num;
1567 }
1568
1569 /**
1570 * Checks if a given frameset is accessable.
1571 *
1572 * @param framesetName
1573 * @return
1574 */
1575 public static Boolean canAccessFrameset(String framesetName) {
1576 framesetName = framesetName.toLowerCase();
1577 for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) {
1578 if ((new File(path + framesetName)).exists()) {
1579 return true;
1580 }
1581 }
1582 return false;
1583 }
1584
1585 public static Frame CreateFrameset(String frameset, String path, boolean recreate) throws InvalidFramesetNameException, ExistingFramesetException {
1586 String conversion = frameset + " --> ";
1587
1588 if (!isValidFramesetName(frameset)) {
1589 throw new InvalidFramesetNameException(frameset);
1590 }
1591
1592 if (!recreate && FrameIO.canAccessFrameset(frameset)) {
1593 throw new ExistingFramesetException(frameset);
1594 }
1595
1596 conversion += frameset;
1597 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Frameset Name: "
1598 + conversion);
1599 conversion = frameset;
1600
1601 /**
1602 * TODO: Update this to exclude any\all invalid filename characters
1603 */
1604 // ignore annotation character
1605 if (frameset.startsWith("@")) {
1606 frameset = frameset.substring(1);
1607 }
1608
1609 conversion += " --> " + frameset;
1610 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Name: " + conversion);
1611
1612 // create the new Frameset directory
1613 File dir = new File(path + frameset.toLowerCase() + File.separator);
1614
1615 // If the directory doesnt already exist then create it...
1616 if (!dir.exists()) {
1617 // If the directory couldnt be created, then there is something
1618 // wrong... ie. The disk is full.
1619 if (!dir.mkdirs()) {
1620 return null;
1621 }
1622 }
1623
1624 // create the new INF file
1625 try {
1626 WriteINF(path, frameset, frameset + '1');
1627 } catch (IOException ioe) {
1628 ioe.printStackTrace();
1629 Logger.Log(ioe);
1630 }
1631
1632 SuspendCache();
1633 // copy the default .0 and .1 files
1634 Frame base = null;
1635 try {
1636 base = LoadFrame(TemplateSettings.DefaultFrame.get());
1637 } catch (Exception e) {
1638 }
1639 // The frame may not be accessed for various reasons... in all these
1640 // cases just create a new one
1641 if (base == null) {
1642 base = new Frame();
1643 }
1644
1645 ResumeCache();
1646
1647 base.reset();
1648 base.resetDateCreated();
1649 base.setFrameset(frameset);
1650 base.setFrameNumber(0);
1651 base.setTitle(base.getFramesetName() + "0");
1652 base.setPath(path);
1653 base.change();
1654 base.setOwner(UserSettings.UserName.get());
1655 SaveFrame(base, false);
1656
1657 base.reset();
1658 base.resetDateCreated();
1659 base.setFrameNumber(1);
1660 base.setTitle(frameset);
1661 base.change();
1662 base.setOwner(UserSettings.UserName.get());
1663 SaveFrame(base, true);
1664
1665 FrameIO.setSavedProperties(base);
1666
1667 Logger.Log(Logger.SYSTEM, Logger.NEW_FRAMESET, "Created new frameset: " + frameset);
1668
1669 return base;
1670 }
1671
1672 /**
1673 * Tests if a frameset name is valid. That is it must begin and end with a
1674 * letter and contain only letters and digits in between.
1675 *
1676 * @param frameset
1677 * the name to be tested
1678 * @return true if the frameset name is valid
1679 */
1680 public static boolean isValidFramesetName(String frameset) {
1681 if (frameset == null) {
1682 return false;
1683 }
1684
1685 int nameLength = frameset.length();
1686 if (frameset.length() <= 0 || nameLength > MAX_NAME_LENGTH) {
1687 return false;
1688 }
1689
1690 int lastCharIndex = nameLength - 1;
1691
1692 if (!Character.isLetter(frameset.charAt(0))
1693 || !Character.isLetter(frameset.charAt(lastCharIndex))) {
1694 return false;
1695 }
1696
1697 for (int i = 1; i < lastCharIndex; i++) {
1698 if (!isValidFrameNameChar(frameset.charAt(i))) {
1699 return false;
1700 }
1701 }
1702 return true;
1703 }
1704
1705 public static boolean deleteFrameset(String framesetName) {
1706 return moveFrameset(framesetName, FrameIO.TRASH_PATH);
1707 }
1708
1709 public static boolean moveFrameset(String framesetName,
1710 String destinationFolder) {
1711 if (!FrameIO.canAccessFrameset(framesetName)) {
1712 return false;
1713 }
1714 // Clear the cache
1715 _Cache.clear();
1716
1717 // Search all the available directories for the directory
1718 for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) {
1719 String source = path + framesetName.toLowerCase() + File.separator;
1720 File framesetDirectory = new File(source);
1721 // Once we have found the directory move it
1722 if (framesetDirectory.exists()) {
1723 String destPath = destinationFolder
1724 + framesetName.toLowerCase();
1725 int copyNumber = 1;
1726 File dest = new File(destPath + File.separator);
1727 // Create the destination folder if it doesnt already exist
1728 if (!dest.getParentFile().exists()) {
1729 dest.mkdirs();
1730 }
1731 // If a frameset with the same name is already in the
1732 // destination add
1733 // a number to the end
1734 while (dest.exists()) {
1735 dest = new File(destPath + ++copyNumber + File.separator);
1736 }
1737 if (!framesetDirectory.renameTo(dest)) {
1738 for (File f : framesetDirectory.listFiles()) {
1739 if (!f.delete()) {
1740 return false;
1741 }
1742 }
1743 if (!framesetDirectory.delete()) {
1744 return false;
1745 }
1746 }
1747 return true;
1748 }
1749 }
1750 return false;
1751 }
1752
1753 public static boolean CopyFrameset(String framesetToCopy,
1754 String copiedFrameset) throws Exception {
1755 if (!FrameIO.canAccessFrameset(framesetToCopy)) {
1756 return false;
1757 }
1758 if (FrameIO.canAccessFrameset(copiedFrameset)) {
1759 return false;
1760 }
1761 // search through all the directories to find the frameset we are
1762 // copying
1763 for (String path : FolderSettings.FrameDirs.getAbsoluteDirs()) {
1764 String source = path + framesetToCopy.toLowerCase()
1765 + File.separator;
1766 File framesetDirectory = new File(source);
1767 if (framesetDirectory.exists()) {
1768 // copy the frameset
1769 File copyFramesetDirectory = new File(path
1770 + copiedFrameset.toLowerCase() + File.separator);
1771 if (!copyFramesetDirectory.mkdirs()) {
1772 return false;
1773 }
1774 // copy each of the frames
1775 for (File f : framesetDirectory.listFiles()) {
1776 // Ignore hidden files
1777 if (f.getName().charAt(0) == '.') {
1778 continue;
1779 }
1780 String copyPath = copyFramesetDirectory.getAbsolutePath()
1781 + File.separator + f.getName();
1782 FrameIO.copyFile(f.getAbsolutePath(), copyPath);
1783 }
1784 return true;
1785 }
1786 }
1787 return false;
1788 }
1789
1790 /**
1791 * Copies a file from one location to another.
1792 *
1793 * @param existingFile
1794 * @param newFileName
1795 * @throws Exception
1796 */
1797 public static void copyFile(String existingFile, String newFileName)
1798 throws IOException {
1799 FileInputStream is = new FileInputStream(existingFile);
1800 FileOutputStream os = new FileOutputStream(newFileName, false);
1801 int data;
1802 while ((data = is.read()) != -1) {
1803 os.write(data);
1804 }
1805 os.flush();
1806 os.close();
1807 is.close();
1808 }
1809
1810 /**
1811 * Saves a frame regardless of whether or not the frame is marked as having
1812 * been changed.
1813 *
1814 * @param frame
1815 * the frame to save
1816 * @return the contents of the frame or null if it could not be saved
1817 */
1818 public static String ForceSaveFrame(Frame frame) {
1819 frame.change();
1820 return SaveFrame(frame, false);
1821 }
1822
1823 public static boolean isValidLink(String frameName) {
1824 return frameName == null || isPositiveInteger(frameName)
1825 || isValidFrameName(frameName);
1826 }
1827
1828 public static void SavePublicFrame(String peerName, String frameName,
1829 int version, BufferedReader packetContents) {
1830 // TODO handle versioning - add version to the header
1831 // Remote user uploads version based on an old version
1832
1833 // Remove it from the cache so that next time it is loaded we get the up
1834 // todate version
1835 _Cache.remove(frameName.toLowerCase());
1836
1837 // Save to file
1838 String filename = PUBLIC_PATH + Conversion.getFramesetName(frameName)
1839 + File.separator + Conversion.getFrameNumber(frameName)
1840 + ExpReader.EXTENTION;
1841
1842 File file = new File(filename);
1843 // Ensure the file exists
1844 if (file.exists()) {
1845 // Check the versions
1846 int savedVersion = ExpReader.getVersion(filename);
1847
1848 if (savedVersion > version) {
1849 // remove this frame from the cache if it is there
1850 // This will make sure links to the original are set correctly
1851 // _Cache.remove(frameName.toLowerCase());
1852
1853 int nextNum = 0;
1854 try {
1855 nextNum = ReadINF(PUBLIC_PATH, Conversion
1856 .getFramesetName(frameName), false) + 1;
1857 } catch (IOException e) {
1858 e.printStackTrace();
1859 }
1860
1861 String newName = Conversion.getFramesetName(frameName)
1862 + nextNum;
1863 filename = PUBLIC_PATH + Conversion.getFramesetName(frameName)
1864 + File.separator + nextNum + ExpReader.EXTENTION;
1865
1866 // Show the messages alerting the user
1867 Text originalMessage = new Text(-1);
1868 originalMessage.setColor(MessageBay.ERROR_COLOR);
1869 originalMessage.setText(frameName + " was edited by "
1870 + peerName);
1871 originalMessage.setLink(frameName);
1872 Text yourMessage = new Text(-1);
1873 yourMessage.setColor(MessageBay.ERROR_COLOR);
1874 yourMessage.setText("Their version was renamed " + newName);
1875 yourMessage.setLink(newName);
1876 MessageBay.displayMessage(originalMessage);
1877 MessageBay.displayMessage(yourMessage);
1878
1879 Frame editedFrame = FrameIO.LoadFrame(frameName);
1880
1881 FrameShare.getInstance().sendMessage(
1882 frameName + " was recently edited by "
1883 + editedFrame.getLastModifyUser(), peerName);
1884 FrameShare.getInstance().sendMessage(
1885 "Your version was renamed " + newName, peerName);
1886 }
1887 }
1888
1889 // Save the new version
1890 try {
1891 // FileWriter fw = new FileWriter(file);
1892
1893 // Open an Output Stream Writer to set encoding
1894 OutputStream fout = new FileOutputStream(file);
1895 OutputStream bout = new BufferedOutputStream(fout);
1896 Writer fw = new OutputStreamWriter(bout, "UTF-8");
1897
1898 String nextLine = null;
1899 while ((nextLine = packetContents.readLine()) != null) {
1900 fw.write(nextLine + '\n');
1901 }
1902 fw.flush();
1903 fw.close();
1904 MessageBay.displayMessage("Saved remote frame: " + frameName);
1905 } catch (IOException e) {
1906 MessageBay.errorMessage("Error remote saving " + frameName + ": "
1907 + e.getMessage());
1908 e.printStackTrace();
1909 }
1910 }
1911
1912 public static void setSavedProperties(Frame toSave) {
1913 toSave.setLastModifyDate(Formatter.getDateTime(), System.currentTimeMillis());
1914 toSave.setLastModifyUser(UserSettings.UserName.get());
1915 toSave.setVersion(toSave.getVersion() + 1);
1916 Time darkTime = new Time(SessionStats.getFrameDarkTime().getTime()
1917 + toSave.getDarkTime().getTime());
1918 Time activeTime = new Time(SessionStats.getFrameActiveTime().getTime()
1919 + toSave.getActiveTime().getTime());
1920 toSave.setDarkTime(darkTime);
1921 toSave.setActiveTime(activeTime);
1922 }
1923
1924 public static Path setupPersonalResources(String username)
1925 {
1926 Path personalResources = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + username);
1927 personalResources.toFile().mkdir();
1928 File[] globalResourcesToCopy = Paths.get(FrameIO.RESOURCES_PRIVATE).toFile().listFiles();
1929
1930 try {
1931 for (File toCopy: globalResourcesToCopy) {
1932 Path p = Paths.get(toCopy.getAbsolutePath());
1933 if (!p.getFileName().toString().equals(".res") && !p.getFileName().toString().equals("about")) {
1934 copyFileTree(p.toAbsolutePath(), personalResources.resolve(p.getFileName()));
1935 }
1936 }
1937 }
1938 catch (IOException e) {
1939 e.printStackTrace();
1940 personalResources = null;
1941 }
1942
1943 return personalResources;
1944 }
1945
1946 private static void copyFileTree(Path source, Path target) throws IOException {
1947 Files.copy(source, target);
1948 if (source.toFile().isDirectory()) {
1949 File[] files = source.toFile().listFiles();
1950 for (File file: files) {
1951 Path asPath = Paths.get(file.getAbsolutePath());
1952 copyFileTree(asPath, target.resolve(asPath.getFileName()));
1953 }
1954 }
1955 }
1956}
Note: See TracBrowser for help on using the repository browser.