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

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

On profile creation a new parameter has been introducted. By passing a map, the user is able to nominate settings frames for which to be notified of their construction.

The above functionalty has been used to programmatically establish the frame number for a users credentials frame.

On user account creation, the users credential frame is migrated to the <username>-credentials folder and a redirect is setup. Functionality for doing this has been generisised and placed in FrameIO.

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