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

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

When loading in messages from the mail databases now uses the timestamp last-accessed companion file to only load in new ones.

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