source: trunk/src/org/apollo/audio/structure/AudioStructureModel.java@ 1434

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

Implementation of ProfileManager. Refactor + additional content for how new profiles are created. The refactoring split out the creation of the default profile from user profiles. Refactoring revealed a long term bug that was causing user profiles to generate with incorrect information. The additional content fixed this bug by introducing the ${USER.NAME} variable, so that the default profile frameset can specify resource locations located in the users resource directory.

org.expeditee.auth.AuthenticatorBrowser
org.expeditee.auth.account.Create
org.expeditee.gui.Browser
org.expeditee.gui.management.ProfileManager
org.expeditee.setting.DirectoryListSetting
org.expeditee.setting.ListSetting
org.expeditee.settings.UserSettings

Implementation of ResourceManager as a core location to get resources from the file system. Also the additional variable ${CURRENT_FRAMESET} to represent the current frameset, so that images can be stored in the directory of the current frameset. This increases portability of framesets.

org.expeditee.gui.FrameIO
org.expeditee.gui.management.ResourceManager
org.expeditee.gui.management.ResourceUtil
Audio:

#NB: Audio used to only operate on a single directory. This has been updated to work in a same way as images. That is: when you ask for a specific resouce, it looks to the user settings to find a sequence of directories to look at in order until it manages to find the desired resource.


There is still need however for a single(ish) source of truth for the .banks and .mastermix file. Therefore these files are now always located in resource-<username>\audio.
org.apollo.agents.MelodySearch
org.apollo.audio.structure.AudioStructureModel
org.apollo.audio.util.MultiTrackPlaybackController
org.apollo.audio.util.SoundDesk
org.apollo.gui.FrameLayoutDaemon
org.apollo.io.AudioPathManager
org.apollo.util.AudioPurger
org.apollo.widgets.FramePlayer
org.apollo.widgets.SampledTrack

Images:

org.expeditee.items.ItemUtils

Frames:

org.expeditee.gui.FrameIO

Fixed a error in the FramePlayer class caused by an incorrect use of toArray().

org.apollo.widgets.FramePlayer


Added several short cut keys to allow for the Play/Pause (Ctrl + P), mute (Ctrl + M) and volume up/down (Ctrl + +/-) when hovering over SampledTrack widgets.

org.apollo.widgets.SampledTrack


Changed the way that Authenticate.login parses the new users profile to be more consistance with other similar places in code.

org.expeditee.auth.account.Authenticate


Encapsulated _body, _surrogateItemsBody and _primaryItemsBody in Frame class. Also changed getBody function to take a boolean flag as to if it should respect the current surrogate mode. If it should then it makes sure that labels have not changed since last time getBody was called.

org.expeditee.gui.Frame

File size: 50.4 KB
Line 
1package org.apollo.audio.structure;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.util.HashMap;
6import java.util.HashSet;
7import java.util.LinkedList;
8import java.util.List;
9import java.util.Map;
10import java.util.Queue;
11import java.util.Set;
12import java.util.Stack;
13
14import javax.sound.sampled.UnsupportedAudioFileException;
15
16import org.apollo.audio.ApolloSubjectChangedEvent;
17import org.apollo.io.AudioIO;
18import org.apollo.mvc.AbstractSubject;
19import org.apollo.mvc.SubjectChangedEvent;
20import org.apollo.util.ExpediteeFileTextSearch;
21import org.apollo.util.Mutable;
22import org.apollo.util.TextItemSearchResult;
23import org.apollo.widgets.LinkedTrack;
24import org.apollo.widgets.SampledTrack;
25import org.apollo.widgets.TrackWidgetCommons;
26import org.expeditee.core.BlockingRunnable;
27import org.expeditee.gio.EcosystemManager;
28import org.expeditee.gui.DisplayController;
29import org.expeditee.gui.Frame;
30import org.expeditee.gui.FrameIO;
31import org.expeditee.gui.management.ResourceManager;
32import org.expeditee.io.Conversion;
33import org.expeditee.items.ItemUtils;
34import org.expeditee.items.widgets.Widget;
35import org.expeditee.settings.folders.FolderSettings;
36
37/**
38 * A thread safe model of the hierarchical structure of a track graph...
39 * abstracted from Expeditee frames and audio widgets.
40 *
41 * The track widgets notify this model for keeping the structure consistent with
42 * Expeditee's data.
43 *
44 *
45 * <b>Thread safe convention:</b>
46 * OverdubbedFrame's and TrackGraphInfo are always handled on the swing thread.
47 * This class provides asynch loading routines but that is purely for loading - not
48 * handling!
49 *
50 * The track graph is a loop-free directed graph.
51 *
52 * A graph root is a graph of OverdubbedFrame's
53 *
54 * Although a {@link TrackGraphNode} or a {@link LinkedTracksGraphNode} may reside
55 * in multiple graphs, there is always only one instance of them - they
56 * are just shared.
57 *
58 * There are multiple graphs because Graphs can be mutually exlusive.
59 * Or some frames cannot be reached unless they are a starting point (aka root)
60 *
61 * @author Brook Novak
62 *
63 */
64public class AudioStructureModel extends AbstractSubject {
65
66 /**
67 * A loop-free directed graph.
68 * For each graph root, there can be no other graph root that is
69 * reachable (other than itself).
70 */
71 private Set<OverdubbedFrame> graphRoots = new HashSet<OverdubbedFrame>(); // SHARED RESOURCE
72
73 /** All overdubbed frames loaded in memory. MAP = Framename -> Overdub instance */
74 private Map<String, OverdubbedFrame> allOverdubbedFrames = new HashMap<String, OverdubbedFrame>(); // SHARED RESOURCE
75
76 /** For resources {@link AudioStructureModel#graphRoots} and {@link AudioStructureModel#allOverdubbedFrames} */
77 private Object sharedResourceLocker = new Object();
78
79 private boolean cancelFetch = false;
80
81 private DelayedModelUpdator delayedModelUpdator = null;
82
83 private static AudioStructureModel instance = new AudioStructureModel();
84
85 private AudioStructureModel() { }
86
87 public static AudioStructureModel getInstance() {
88 return instance;
89 }
90
91 /**
92 * <b>MUST NOT BE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
93 *
94 * Same as {@link #fetchGraph(String)} but waits for any updates to finish before
95 * the fetch.
96 *
97 * Intention: really to highlight the need to call waitOnUpdates prior to this ... but
98 * may not bee neccessary...
99 *
100 * @param rootFrameName
101 * refer to #fetchGraph(String)
102 * @return
103 * refer to #fetchGraph(String)
104 *
105 * @throws NullPointerException
106 * if rootFrameName is null.
107 *
108 * @throws IllegalArgumentException
109 * If rootFrameName is not a valid framename.
110 *
111 * @throws InterruptedException
112 * refer to #fetchGraph(String)
113 *
114 * @throws TrackGraphLoopException
115 * refer to #fetchGraph(String) -
116 * <B>However</B> - it could also be that the reason was due to the thread being interupted while
117 * waiting on a update to finish. Thus may want to manually wait for updates prior - see
118 * {@link #waitOnUpdates()}.
119 *
120 * @see #fetchGraph(String)
121 */
122 public synchronized OverdubbedFrame fetchLatestGraph(String rootFrameName)
123 throws InterruptedException, TrackGraphLoopException {
124 waitOnUpdates();
125 return fetchGraph(rootFrameName);
126 }
127
128 /**
129 * May have to read from file if not yet loaded in memory.
130 * Thus it could take some time.
131 *
132 * Thread safe.
133 *
134 * NOTE: The intention is that this is called on a dedicated thread... other than
135 * swings thread. However once a OverdubbedFrame is returned be sure
136 * to only use it on the swing thread by convention.
137 *
138 * <b>MUST NOT NE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
139 *
140 * @param rootFrameName
141 * Must not be null. Mustn't be a link - must be the <i>framename</i>
142 *
143 * @return
144 * The overdubbed frame.
145 * Null if the frame does not exist - or if it does exist but there
146 * are no track / linked-track widgets on it. Not that it can return
147 * a overdubbed frame that contains a heirarchy of linked overdubbed frames
148 * but no actual playable tracks.
149 *
150 * @throws NullPointerException
151 * if rootFrameName is null.
152 *
153 * @throws IllegalArgumentException
154 * If rootFrameName is not a valid framename.
155 *
156 * @throws InterruptedException
157 * If the fetch request was cancelled because the model
158 * has changed in some way during the read. Must retry the fetch.
159 *
160 * @throws TrackGraphLoopException
161 * If the requested root introduces loops to the current graph state.
162 * The loop trace will be provided in the exception.
163 *
164 * @see TrackGraphLoopException#getFullLoopTrace()
165 *
166 */
167 public synchronized OverdubbedFrame fetchGraph(String rootFrameName)
168 throws InterruptedException, TrackGraphLoopException {
169
170 if(rootFrameName == null) throw new NullPointerException("rootFrameName");
171 if(!FrameIO.isValidFrameName(rootFrameName))
172 throw new IllegalArgumentException("the rootFrameName \""
173 + rootFrameName +"\" is not a valid framename");
174
175 // reset flag
176 cancelFetch = false; // note that race conditions here is beside the point...meaningless
177
178 OverdubbedFrame rootODFrame = null;
179 synchronized(sharedResourceLocker) {
180 rootODFrame = allOverdubbedFrames.get(rootFrameName.toLowerCase());
181 }
182
183 if (rootODFrame != null) {
184 return rootODFrame;
185 }
186
187 // There is no overdub frame loaded for the requested frame.
188 // Thus create a new root
189 Map<String, OverdubbedFrame> newGraph = new HashMap<String, OverdubbedFrame> ();
190 rootODFrame = buildGraph(rootFrameName, newGraph); // throws InterruptedException's
191
192 // There exists no such frame or is not an actual overdubbed frame
193 if (rootODFrame == null || rootODFrame.isEmpty()) return null;
194
195 // Must run on swing thread for checking for loops before commiting the new graph to
196 // the model.
197 NewGraphCommitor commit = new NewGraphCommitor(rootODFrame, newGraph);
198 try {
199 EcosystemManager.getMiscManager().runOnGIOThread(commit);
200 } catch (Exception e) {
201 e.printStackTrace();
202 assert(false);
203 }
204
205 // Check if commit aborted
206 if (commit.abortedCommit) {
207
208 if (cancelFetch) { // Due to cancel request?
209 throw new InterruptedException();
210 }
211
212 // Must be due to loop
213 assert (!commit.loopTrace.isEmpty());
214 throw new TrackGraphLoopException(commit.loopTrace);
215 }
216
217 return rootODFrame;
218 }
219
220 /**
221 * Assumption: that this is called from another thread other than the swing thread.
222 *
223 * @param rootFrameName
224 *
225 * @param newNodes
226 *
227 * @return
228 * Null if rootFrameName does not exist
229 *
230 * @throws InterruptedException
231 */
232 private OverdubbedFrame buildGraph(String rootFrameName, Map<String, OverdubbedFrame> newNodes)
233 throws InterruptedException
234 {
235 // Must be a frame name, not a link
236 assert(rootFrameName != null);
237 assert(FrameIO.isValidFrameName(rootFrameName));
238
239 // If cancelled the immediatly abort fetch
240 if (cancelFetch) { // check for cancel request
241 throw new InterruptedException();
242 }
243
244 // Look for existing node on previously loaded graph...
245 OverdubbedFrame oframe = null;
246 synchronized(sharedResourceLocker) {
247 oframe = allOverdubbedFrames.get(rootFrameName.toLowerCase());
248 }
249
250 // Check to see if not already created this node during this recursive call
251 if (oframe == null)
252 oframe = newNodes.get(rootFrameName.toLowerCase());
253
254 if (oframe != null) return oframe;
255
256 // There are no existing nodes ... thus create a new node from searching for
257 // the frame from file
258
259 // But first .. look in expeditee's cache since the cache might not be consistent with the file system yet
260 // .. or in the common case - the root could be the current frame.
261 ExpediteeCachedTrackInfoFetcher cacheFetch = new ExpediteeCachedTrackInfoFetcher(rootFrameName);
262
263 try {
264 EcosystemManager.getMiscManager().runOnGIOThread(cacheFetch);
265 } catch (Exception e) {
266 e.printStackTrace();
267 assert(false);
268 }
269
270 /* Localfilename -> TrackModelData. */
271 Map<String, TrackModelData> tracks = null;
272
273 /* VirtualFilename -> LinkedTrackModelData. */
274 Map<String, LinkedTrackModelData> linkedTracks = null;
275
276 if (cacheFetch.tracks != null) {
277
278 tracks = cacheFetch.tracks;
279 assert(cacheFetch.linkedTracks != null);
280 linkedTracks = cacheFetch.linkedTracks;
281
282 } else { // search on file system
283
284 String trackPrefix = ItemUtils.GetTag(ItemUtils.TAG_IWIDGET) + ": ";
285 String linkedTrackPrefix = trackPrefix;
286
287 trackPrefix += SampledTrack.class.getName();
288 linkedTrackPrefix += LinkedTrack.class.getName();
289
290 String fullPath = null;
291 List<String> possiblePaths = FolderSettings.FrameDirs.getAbsoluteDirs();
292
293 for (int i = 0; i < possiblePaths.size(); i++) { // RISKY CODE - IN EXPEDITEE SPACE FROM RANDOM TRHEAD
294 String possiblePath = possiblePaths.get(i);
295 fullPath = FrameIO.getFrameFullPathName(possiblePath, rootFrameName);
296 if (fullPath != null)
297 break;
298 }
299
300 // does even exist?
301 if (fullPath == null) {
302 return null;
303 }
304
305 try {
306
307 // Perform prefix search
308 List<TextItemSearchResult> results = ExpediteeFileTextSearch.prefixSearch(
309 fullPath,
310 new String[] {trackPrefix, linkedTrackPrefix});
311
312 // Parse search results
313 tracks = new HashMap<String, TrackModelData>();
314 linkedTracks = new HashMap<String, LinkedTrackModelData>();
315
316 for (TextItemSearchResult result : results) {
317
318 // Track widget
319 if (result.text.startsWith(trackPrefix)) {
320
321 String name = null;
322 String localFileName = null;
323 Mutable.Long initiationTime = null;
324
325 for (String data : result.data) { // read data lines
326 data = data.trim();
327
328 if (data.startsWith(SampledTrack.META_LOCALNAME_TAG) &&
329 data.length() > SampledTrack.META_LOCALNAME_TAG.length()) {
330 localFileName = data.substring(SampledTrack.META_LOCALNAME_TAG.length());
331
332 } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
333 && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
334
335 try {
336 initiationTime = Mutable.createMutableLong(Long.parseLong(
337 data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
338 } catch (NumberFormatException e) { /* Consume */ }
339
340 } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
341 && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
342
343 name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
344
345 }
346 }
347
348 // Add track to map
349 if (localFileName != null) {
350 tracks.put(localFileName, new TrackModelData(
351 initiationTime, -1, name, result.position.getY())); // pass -1 for running time to signify that must be read from audio file
352 }
353
354 // Linked track widget
355 } else {
356
357 assert(result.text.startsWith(linkedTrackPrefix));
358
359 // If the linked track infact has a link
360 if (result.explink != null && result.explink.length() > 0) {
361
362 Mutable.Long initiationTime = null;
363 String virtualFilename = null;
364 String name = null;
365
366 for (String data : result.data) { // read data lines
367 data = data.trim();
368
369 // OK OK, Smell in code here - duplicated from above. - sorta
370 if (data.startsWith(LinkedTrack.META_VIRTUALNAME_TAG) &&
371 data.length() > LinkedTrack.META_VIRTUALNAME_TAG.length()) {
372 virtualFilename = data.substring(LinkedTrack.META_VIRTUALNAME_TAG.length());
373
374 } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
375 && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
376
377 try {
378 initiationTime = Mutable.createMutableLong(Long.parseLong(
379 data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
380 } catch (NumberFormatException e) { /* Consume */ }
381
382 } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
383 && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
384
385 name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
386
387 }
388 }
389
390 // Add linked track to map
391 if (virtualFilename != null) {
392 linkedTracks.put(virtualFilename, new LinkedTrackModelData(
393 initiationTime, result.explink, name, result.position.getY())); // pass -1 for running time to signify that must be read from audio file
394 }
395
396 }
397
398
399 }
400
401 } // Proccess next result
402
403 } catch (FileNotFoundException e) {
404 e.printStackTrace();
405 return null;
406 } catch (IOException e) {
407 e.printStackTrace();
408 return null;
409 }
410
411 }
412
413 if (cancelFetch) { // check for cancel request
414 throw new InterruptedException();
415 }
416
417 assert(tracks != null);
418 assert(linkedTracks != null);
419
420 // Add new node (avoid infite recursion)
421 oframe = new OverdubbedFrame(rootFrameName);
422 newNodes.put(rootFrameName.toLowerCase(), oframe);
423
424 // Load track times from file
425 for (String localFilename : tracks.keySet()) {
426
427 if (cancelFetch) { // check for cancel request
428 throw new InterruptedException();
429 }
430
431 TrackModelData tmodel = tracks.get(localFilename);
432 if (tmodel.runningTimeMS <= 0) {
433 try {
434
435 String localFilePath = ResourceManager.getAudioResource(localFilename, DisplayController.getCurrentFrame()).getAbsolutePath();
436 tmodel.runningTimeMS = AudioIO.getRunningTime(localFilePath);
437
438 } catch (IOException e) {
439 e.printStackTrace();
440 } catch (UnsupportedAudioFileException e) {
441 e.printStackTrace();
442 }
443 }
444
445 // If was able to get the runnning time ... then include in model
446 if (tmodel.runningTimeMS > 0) {
447
448 if (tmodel.initiationTime == null) {
449 tmodel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
450 // Remember: initiation times are relative, so setting to zero
451 // could be obscure.
452 }
453
454 oframe.addTrack(new TrackGraphNode(
455 tmodel.initiationTime.value,
456 localFilename,
457 tmodel.runningTimeMS,
458 tmodel.name,
459 tmodel.ypos));
460
461 } // otheriwse omit from model.
462
463 }
464
465 // Get linked OverdubbedFrame's.. recurse
466 for (String virtualFilename : linkedTracks.keySet()) {
467
468 LinkedTrackModelData linkedTrackModel = linkedTracks.get(virtualFilename);
469
470 // At this point, the link may / maynot be absolute,
471 String linkedFrameName = linkedTrackModel.frameLink;
472 assert(linkedFrameName != null);
473
474 if (linkedFrameName.length() == 0) continue;
475
476 if (!(Character.isLetter(linkedFrameName.charAt(0)))) { // relative link
477 // Heres a trick: to get the frameset of the relative link - its just the
478 // current root framenames frameset
479 linkedFrameName = Conversion.getFramesetName(rootFrameName) + linkedFrameName; // WARNING: IN EXPEDITEE THREAD
480 }
481
482 OverdubbedFrame od = buildGraph(linkedFrameName, newNodes); // Recurse
483
484 if (od == null) { // bad link or empty frame
485 od = new OverdubbedFrame(linkedFrameName);
486 }
487
488 if (linkedTrackModel.initiationTime == null) {
489 linkedTrackModel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
490 // Remember: initiation times are relative, so setting to zero
491 // could be obscure.
492 }
493
494 oframe.addLinkedTrack(new LinkedTracksGraphNode(
495 linkedTrackModel.initiationTime.value,
496 od,
497 virtualFilename,
498 linkedTrackModel.name,
499 linkedTrackModel.ypos));
500
501 }
502
503 return oframe;
504 }
505
506 /**
507 *
508 * @author Brook Novak
509 *
510 */
511 private class TrackModelData {
512
513 TrackModelData(Mutable.Long initiationTime, long runningTimeMS, String name, int ypos) {
514 this.initiationTime = initiationTime;
515 this.runningTimeMS = runningTimeMS;
516 this.name = name;
517 this.ypos = ypos;
518 }
519
520 /** Null if unavilable */
521 Mutable.Long initiationTime;
522
523 /** negative if unavailable */
524 long runningTimeMS;
525
526 /** Can be null. Trackname */
527 String name;
528
529 int ypos;
530 }
531
532 /**
533 *
534 * @author Brook Novak
535 *
536 */
537 private class LinkedTrackModelData {
538
539 LinkedTrackModelData(Mutable.Long initiationTime, String frameLink, String name, int ypos) {
540 assert(frameLink != null);
541 this.initiationTime = initiationTime;
542 this.frameLink = frameLink;
543 this.name = name;
544 this.ypos = ypos;
545 }
546 /** Null if unavilable */
547 Mutable.Long initiationTime;
548
549 /** The framename */
550 String frameLink;
551
552 /** Can be null. Trackname */
553 String name;
554
555 /** The y pixel position */
556 int ypos;
557 }
558
559 /**
560 * Used for fetching track info in expeditees cache
561 *
562 * @author Brook Novak
563 *
564 */
565 private class ExpediteeCachedTrackInfoFetcher extends BlockingRunnable
566 {
567 private String rootFrameName;
568
569 /** Localfilename -> TrackModelData. Not null if frame was in memory. */
570 Map<String, TrackModelData> tracks = null;
571
572 /** VirtualFilename -> LinkedTrackModelData. Not null if frame was in memory. */
573 Map<String, LinkedTrackModelData> linkedTracks = null;
574
575 ExpediteeCachedTrackInfoFetcher(String rootFrameName)
576 {
577 assert(rootFrameName != null);
578 this.rootFrameName = rootFrameName;
579 }
580
581 public void execute()
582 {
583 assert(rootFrameName != null);
584
585 // Check if the current frame
586 Frame rootFrame = null;
587 if (DisplayController.getCurrentFrame() != null && DisplayController.getCurrentFrame().getName() != null
588 && DisplayController.getCurrentFrame().getName().equals(rootFrameName)) {
589 rootFrame = DisplayController.getCurrentFrame();
590 } else {
591 // Check if in cache
592 rootFrame = FrameIO.FrameFromCache(rootFrameName);
593 }
594
595 // Frame exists in memory... rummage around for track meta data
596 if (rootFrame != null) {
597 tracks = new HashMap<String, TrackModelData>();
598 linkedTracks = new HashMap<String, LinkedTrackModelData>();
599
600 for (Widget iw : rootFrame.getInteractiveWidgets()) {
601
602 if (iw instanceof SampledTrack) {
603 SampledTrack sampledTrackWidget = (SampledTrack)iw;
604
605 TrackGraphNode tinf =
606 AudioStructureModel.getInstance().getTrackGraphInfo(
607 sampledTrackWidget.getLocalFileName(),
608 rootFrameName);
609
610 Mutable.Long initTime = (tinf != null) ? Mutable.createMutableLong(tinf.getInitiationTime()) :
611 sampledTrackWidget.getInitiationTimeFromMeta();
612
613 long runningTime = sampledTrackWidget.getRunningMSTimeFromRawAudio();
614 if (runningTime <= 0) runningTime = -1; // signify to load from file
615
616 tracks.put(sampledTrackWidget.getLocalFileName(),
617 new TrackModelData(
618 initTime,
619 runningTime,
620 sampledTrackWidget.getName(),
621 sampledTrackWidget.getY()));
622
623 // NOTE: If the audio was sotred in a recovery file - then the widget would
624 // be deleted - thus no need to consider recovery files.
625
626 } else if (iw instanceof LinkedTrack) {
627 LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
628
629 LinkedTracksGraphNode ltinf =
630 AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
631 linkedTrackWidget.getVirtualFilename(),
632 rootFrameName);
633
634 Mutable.Long initTime = (ltinf != null) ? Mutable.createMutableLong(ltinf.getInitiationTime()) :
635 linkedTrackWidget.getInitiationTimeFromMeta();
636
637 // Don't consider track-links without any links
638 if (linkedTrackWidget.getLink() != null) {
639 linkedTracks.put(linkedTrackWidget.getVirtualFilename(),
640 new LinkedTrackModelData(
641 initTime,
642 linkedTrackWidget.getLink(),
643 linkedTrackWidget.getName(),
644 linkedTrackWidget.getY()));
645 }
646
647 }
648
649 }
650
651 }
652 }
653 }
654
655
656 /**
657 * Used for commiting a new graph in a thread-safe way.
658 * Checks for loops in the graph before commiting.
659 *
660 * If did not commit, then {@link #abortedCommit} will be true. Otherwise it will be false.
661 *
662 * if {@link #loopTrace} is not empty after the run, then the commit
663 * was aborted because there was a loop. (And {@link #loopTrace} contains the trace).
664 * The only other reason for aborting the commit was if a cancel was requested
665 * (@see TrackGraphModel#cancelFetch)
666 *
667 * @author Brook Novak
668 *
669 *
670 */
671 private class NewGraphCommitor extends BlockingRunnable
672 {
673 private final OverdubbedFrame rootODFrame;
674 private final Map<String, OverdubbedFrame> newGraph;
675
676 private boolean abortedCommit = false;
677
678 /** If not empty after the run, then the commit */
679 Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
680
681 NewGraphCommitor(OverdubbedFrame rootODFrame, Map<String, OverdubbedFrame> newGraph)
682 {
683 assert(rootODFrame != null);
684 assert(newGraph != null);
685
686 this.rootODFrame = rootODFrame;
687 this.newGraph = newGraph;
688 }
689
690 public void execute()
691 {
692
693 // Theoretically this would never need to be called ... but for super safety
694 // keep sync block - since dealing with shared resources.
695 synchronized(sharedResourceLocker) {
696
697 // At anytime during this point, the new graph could become invalid
698 // during this time while officially adding it to the "consistant" model..
699 // which is obviously BAD.
700 if (cancelFetch) {
701 abortedCommit = true;
702 return; // important: while locked shared resources
703 }
704
705 // Check that the graph is loop free.
706 boolean ilf = isLoopFree(rootODFrame, loopTrace);
707 if (ilf) { // check from existing roots POV's
708 for (OverdubbedFrame existingRoot : graphRoots) {
709 loopTrace.clear();
710 ilf = isLoopFree(existingRoot, loopTrace);
711 if (!ilf) break;
712 }
713 }
714
715 if (!ilf) {
716 assert (!loopTrace.isEmpty()); // loopTrace will contain the trace
717 abortedCommit = true;
718 return;
719 } else {
720 assert (loopTrace.isEmpty());
721 }
722
723 // Since we are creating a new root, existing roots might
724 // be reachable from the new root, thus remove reachable
725 // roots ... since they are no longer roots ...
726 List<OverdubbedFrame> redundantRoots = new LinkedList<OverdubbedFrame>();
727 for (OverdubbedFrame existingRoot : graphRoots) {
728 if (rootODFrame.getChild(existingRoot.getFrameName()) != null) {
729 redundantRoots.add(existingRoot); // this root is reachable from the new root
730 }
731 }
732
733 // Get rid of the redundant roots
734 graphRoots.removeAll(redundantRoots);
735
736 // Commit the new loop-free graph to the model
737 graphRoots.add(rootODFrame); // add the mutex or superceeding root
738 allOverdubbedFrames.putAll(newGraph); // Include new graph into all allOverdubbedFrames
739 }
740 }
741 }
742//
743// /**
744// * Determines if a node is reachable from a given node.
745// *
746// * @param source
747// * Must not be null.
748// *
749// * @param target
750// * Must not be null.
751// *
752// * @param visited
753// * Must be empty. Must not be null.
754// *
755// * @return
756// * True if target is reachable via source.
757// */
758// private boolean isReachable(OverdubbedFrame source, OverdubbedFrame target, Set<OverdubbedFrame> visited) {
759// assert(source != null);
760// assert(target != null);
761// assert(visited != null);
762//
763// // Base cases
764// if (source == target) return true;
765// else if (visited.contains(source)) return false;
766//
767// // Remember visited node
768// visited.add(source);
769//
770// // Recurse
771// for (Iterator<LinkedTracksGraphInfo> itor = source.getLinkedTrackIterator(); itor.hasNext();) {
772// LinkedTracksGraphInfo lti = itor.next();
773//
774// if (isReachable(lti.getLinkedFrame(), target, visited)) {
775// return true;
776// }
777// }
778//
779// // Reached end of this nodes links - found no match
780// return false;
781// }
782
783 /**
784 * Determines if a graph is loop free from a starting point.
785 * <B>MUST BE ON THE SWING THREAD</B>
786 *
787 * @param current
788 * Where to look for loops from.
789 *
790 * @param visitedStack
791 * Must be empty. Used internally. If the
792 * function returns false, then the stack will provde a trace of the loop.
793 *
794 * @return
795 * True if the graph at current is loop free.
796 */
797 private boolean isLoopFree(OverdubbedFrame current, Stack<OverdubbedFrame> visitedStack) {
798
799 if (visitedStack.contains(current)) { // found a loop
800 visitedStack.add(current); // add to strack for loop trace.
801 return false;
802 }
803
804 visitedStack.push(current); // save current node to stack
805
806 for (LinkedTracksGraphNode tl : current.getLinkedTracksCopy()) {
807
808 boolean ilf = isLoopFree(tl.getLinkedFrame(), visitedStack);
809
810 if (!ilf) return false;
811
812 }
813
814 visitedStack.pop(); // pop the current node
815
816 return true;
817
818 }
819
820 /**
821 * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
822 *
823 * @param localFilename
824 * Must not be null.
825 *
826 * @param parentFrameName
827 * If null - the search will be slower.
828 *
829 * @return
830 * The TrackGraphInfo for the given track. Null if not in model
831 */
832 public TrackGraphNode getTrackGraphInfo(String localFilename, String parentFrameName) {
833
834 synchronized(sharedResourceLocker) {
835
836 assert(localFilename != null);
837
838 if (parentFrameName != null) {
839 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
840
841
842 if (odframe != null) {
843 return odframe.getTrack(localFilename);
844 }
845
846 } else { // parentFrameName is null .. do search
847
848 for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
849 TrackGraphNode tinf = odframe.getTrack(localFilename);
850 if (tinf != null) return tinf;
851 }
852 }
853 }
854
855 return null;
856 }
857
858
859 /**
860 * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
861 *
862 * @param virtualFilename
863 * Must not be null.
864 *
865 * @param parentFrameName
866 * If null - the search will be slower.
867 *
868 * @return
869 * The LinkedTracksGraphInfo for the given virtualFilename. Null if not in model
870 */
871 public LinkedTracksGraphNode getLinkedTrackGraphInfo(String virtualFilename, String parentFrameName) {
872
873 synchronized(sharedResourceLocker) {
874
875 assert(virtualFilename != null);
876
877 if (parentFrameName != null) {
878 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
879
880
881 if (odframe != null) {
882 return odframe.getLinkedTrack(virtualFilename);
883 }
884
885 } else {
886
887 for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
888 LinkedTracksGraphNode ltinf = odframe.getLinkedTrack(virtualFilename);
889 if (ltinf != null) return ltinf;
890 }
891
892 }
893 }
894
895 return null;
896 }
897
898
899 /**
900 * <B>MUST BE ON SWING THREAD</B>.
901 * Gets the parent ODFrame of a track - given the tracks local filename.
902 *
903 * @param localFilename
904 * The local filename of the track to get the frame for. Must not be null.
905 *
906 * @return
907 * The parent. Null if no parent exists.
908 */
909 public OverdubbedFrame getParentOverdubbedFrame(String localFilename) {
910 assert(localFilename != null);
911
912 synchronized (allOverdubbedFrames) {
913 for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
914 if (odf.containsTrack(localFilename)) {
915 return odf;
916 }
917 }
918 }
919
920 return null;
921 }
922
923 /**
924 * Gets an overdubbed frame representation of a given frame.
925 *
926 * @param framename
927 * The name of the frame.
928 *
929 * @return
930 * The OverdubbedFrame for the given framename. Null if does not exist.
931 */
932 public OverdubbedFrame getOverdubbedFrame(String framename) {
933 assert(framename != null);
934
935 synchronized (allOverdubbedFrames) {
936 return allOverdubbedFrames.get(framename.toLowerCase());
937 }
938 }
939
940 /**
941 * <B>MUST BE ON SWING THREAD</B>.
942 * Keeps model consistent with expeditee.
943 *
944 * @param localFilename
945 * Must not be null.
946 *
947 * @param parentFrameName
948 * Can be null.
949 *
950 * @param currentRunningTime
951 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
952 */
953 public void onTrackWidgetAudioEdited(String localFilename, String parentFrameName, long currentRunningTime) {
954
955 boolean doNotify = false;
956 TrackGraphNode tinf = null;
957
958 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
959
960 assert(localFilename != null);
961 assert (currentRunningTime > 0);
962
963 // Locate parent frame
964 OverdubbedFrame odframe = null;
965
966 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
967 else odframe = getParentOverdubbedFrame(localFilename);
968
969 // adjust running time in model
970 if (odframe != null) { // is loaded?
971 tinf = odframe.getTrack(localFilename);
972 assert(tinf != null); // due to the assumption that the model is consistent
973 if (tinf.getRunningTime() != currentRunningTime) {
974 tinf.setRunningTime(currentRunningTime);
975 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
976 // It will die in its own time - and will always be cancelled because it locks
977 // the current locked object.
978 cancelFetch = doNotify = true;
979 }
980 }
981
982
983 }
984
985 // Notify observers.
986 if (doNotify)
987 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_EDITED, tinf));
988 }
989
990 /**
991 * <B>MUST BE ON SWING THREAD</B>.
992 * Keeps model consistent with expeditee.
993 *
994 * @param localFilename
995 * Must not be null.
996 *
997 * @param parentFrameName
998 * Can be null.
999 *
1000 * @param newInitiationTime
1001 * In milliseconds.Relative - i.e. can be negative
1002 *
1003 * @param name
1004 * The name given to the linked track. Can be null if there is no name...
1005 *
1006 * @param ypos
1007 * The Y-pixel position of the track.
1008 *
1009 * @param currentRunningTime
1010 * Must be larger than zero. In milliseconds. Used in case the model has not been create for the widget.
1011 */
1012 public void onTrackWidgetAnchored(
1013 String localFilename, String parentFrameName,
1014 long newInitiationTime, long currentRunningTime,
1015 String name, int ypos) {
1016
1017 boolean doNotify = false;
1018 TrackGraphNode tinf = null;
1019
1020 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1021
1022 assert(localFilename != null);
1023
1024 // Locate parent frame
1025 OverdubbedFrame odframe = null;
1026
1027 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1028 else odframe = getParentOverdubbedFrame(localFilename);
1029
1030 // adjust initiation time in model
1031 if (odframe != null) { // is loaded?
1032 tinf = odframe.getTrack(localFilename);
1033
1034 if (tinf != null && tinf.getInitiationTime() != newInitiationTime) {
1035 tinf.setInitiationTime(newInitiationTime);
1036 cancelFetch = doNotify = true;
1037
1038 } else { // if there is no model but overdub frame is in memory - then create new model for this track.
1039
1040 tinf = new TrackGraphNode(
1041 newInitiationTime,
1042 localFilename,
1043 currentRunningTime,
1044 name,
1045 ypos);
1046
1047 odframe.addTrack(tinf); // safe because on swing thread
1048
1049 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1050 cancelFetch = doNotify = true;
1051 // It will die in its own time - and will always be cancelled because it locks
1052 // the current locked object.
1053 }
1054
1055
1056 }
1057
1058 }
1059
1060 // Notify observers.
1061 if (doNotify)
1062 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_ADDED, tinf));
1063
1064 }
1065
1066 /**
1067 * <B>MUST BE ON SWING THREAD</B>.
1068 * Keeps model consistent with expeditee.
1069 *
1070 * Note: Invoke when picked up - not when a frame changes... also if it
1071 * is removed due to XRaymode - in that case the whole model will be released.
1072 *
1073 * @param localFilename
1074 * Must not be null.
1075 *
1076 * @param parentFrameName
1077 * Can be null. If given will be faster.
1078 *
1079 */
1080 public void onTrackWidgetRemoved(String localFilename, String parentFrameName) {
1081 TrackGraphNode tinf = null;
1082 boolean doNotify = false;
1083
1084 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1085
1086 if (DisplayController.isXRayMode()) { // discard whole model
1087
1088 // Neccessary because if the user goes into xray then moves to a new frame
1089 // then it wil screw everything up. Also note they can delete the text source items
1090 // in xray mode.
1091 // This basically will cause short load times to occur again...
1092 allOverdubbedFrames.clear();
1093 graphRoots.clear();
1094
1095 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1096 cancelFetch = doNotify = true;
1097 // It will die in its own time - and will always be cancelled because it locks
1098 // the current locked object.
1099
1100 } else {
1101
1102 // Locate parent frame
1103 OverdubbedFrame odframe = null;
1104
1105 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1106 else odframe = getParentOverdubbedFrame(localFilename);
1107
1108 if (odframe != null) {
1109 tinf = odframe.removeTrack(localFilename);
1110 //assert(tinf != null);
1111 cancelFetch = doNotify = true;
1112 }
1113
1114 }
1115
1116 }
1117
1118 // Notify observers.
1119 if (doNotify)
1120 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_REMOVED, tinf));
1121 }
1122
1123 /**
1124 * <B>MUST BE ON SWING THREAD</B>.
1125 * Keeps model consistent with expeditee.
1126 *
1127 * @param localFilename
1128 * Must not be null.
1129 *
1130 * @param parentFrameName
1131 * Can be null.
1132 *
1133 * @param currentRunningTime
1134 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
1135 */
1136 public void onTrackWidgetNameChanged(String localFilename, String parentFrameName, String newName) {
1137
1138 boolean doNotify = false;
1139
1140 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1141
1142 assert(localFilename != null);
1143
1144 // Locate parent frame
1145 OverdubbedFrame odframe = null;
1146
1147 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1148 else odframe = getParentOverdubbedFrame(localFilename);
1149
1150 // adjust name in model
1151 if (odframe != null) { // is loaded?
1152 AbstractTrackGraphNode tinf = odframe.getTrack(localFilename);
1153 assert(tinf != null); // due to the assumption that the model is consistent
1154 if (tinf.getName() != newName) {
1155 tinf.setName(newName);
1156 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1157 cancelFetch = doNotify = true;
1158 // It will die in its own time - and will always be cancelled because it locks
1159 // the current locked object.
1160 }
1161 }
1162
1163 }
1164
1165 // Notify observers.
1166 if (doNotify)
1167 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, localFilename));
1168 }
1169
1170
1171 /**
1172 * <B>MUST BE ON SWING THREAD</B>.
1173 * Keeps model consistent with expeditee.
1174 *
1175 * @param localFilename
1176 * Must not be null.
1177 *
1178 * @param parentFrameName
1179 * Can be null.
1180 *
1181 * @param initTime
1182 * The initiation time in ms
1183 *
1184 * @param yPos
1185 * The y pixel position.
1186 *
1187 */
1188 public void onTrackWidgetPositionChanged(String localFilename, String parentFrameName, long initTime, int yPos) {
1189
1190 boolean doNotify = false;
1191 AbstractTrackGraphNode tinf = null;
1192
1193 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1194
1195 assert(localFilename != null);
1196
1197 // Locate parent frame
1198 OverdubbedFrame odframe = null;
1199
1200 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1201 else odframe = getParentOverdubbedFrame(localFilename);
1202
1203 // adjust name in model
1204 if (odframe != null) { // is loaded?
1205 tinf = odframe.getTrack(localFilename);
1206 if (tinf != null &&
1207 (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
1208 tinf.setInitiationTime(initTime);
1209 tinf.setYPixelPosition(yPos);
1210 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1211 cancelFetch = doNotify = true;
1212 // It will die in its own time - and will always be cancelled because it locks
1213 // the current locked object.
1214 }
1215 }
1216
1217 }
1218
1219 // Notify observers.
1220 if (doNotify)
1221 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_POSITION_CHANGED, tinf));
1222 }
1223
1224
1225 /**
1226 * <B>MUST BE ON SWING THREAD</B>.
1227 * Keeps model consistent with expeditee.
1228 *
1229 * @param virtualFilename
1230 * Must not be null.
1231 *
1232 * @param parentFrameName
1233 * Can be null.
1234 *
1235 * @param initTime
1236 * The initiation time in ms
1237 *
1238 * @param yPos
1239 * The y pixel position.
1240 *
1241 */
1242 public void onLinkedTrackWidgetPositionChanged(String virtualFilename, String parentFrameName, long initTime, int yPos) {
1243
1244 boolean doNotify = false;
1245 AbstractTrackGraphNode tinf = null;
1246
1247 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1248
1249 assert(virtualFilename != null);
1250
1251 // Locate parent frame
1252 OverdubbedFrame odframe = null;
1253
1254 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1255 else odframe = getParentOverdubbedFrame(virtualFilename);
1256
1257 // adjust name in model
1258 if (odframe != null) { // is loaded?
1259 tinf = odframe.getLinkedTrack(virtualFilename);
1260 if (tinf != null &&
1261 (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
1262
1263 tinf.setInitiationTime(initTime);
1264 tinf.setYPixelPosition(yPos);
1265
1266 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1267 cancelFetch = doNotify = true;
1268 // It will die in its own time - and will always be cancelled because it locks
1269 // the current locked object.
1270 }
1271 }
1272 }
1273
1274 // Notify observers.
1275 if (doNotify)
1276 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_POSITION_CHANGED, tinf));
1277 }
1278
1279 /**
1280 * <B>MUST BE ON SWING THREAD</B>.
1281 * Keeps model consistent with expeditee.
1282 *
1283 * @param virtualFilename
1284 * Must not be null.
1285 *
1286 * @param parentFrameName
1287 * Can be null.
1288 *
1289 * @param currentRunningTime
1290 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
1291 */
1292 public void onLinkedTrackWidgetNameChanged(String virtualFilename, String parentFrameName, String newName) {
1293
1294 boolean doNotify = false;
1295
1296 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1297
1298 assert(virtualFilename != null);
1299
1300 // Locate parent frame
1301 OverdubbedFrame odframe = null;
1302
1303 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1304 else odframe = getParentOverdubbedFrame(virtualFilename);
1305
1306 // adjust name in model
1307 if (odframe != null) { // is loaded?
1308 AbstractTrackGraphNode tinf = odframe.getLinkedTrack(virtualFilename);
1309 if (tinf != null && tinf.getName() != newName) {
1310 tinf.setName(newName);
1311 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1312 cancelFetch = doNotify = true;
1313 // It will die in its own time - and will always be cancelled because it locks
1314 // the current locked object.
1315 }
1316 }
1317 }
1318
1319 // Notify observers.
1320 if (doNotify)
1321 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, virtualFilename));
1322 }
1323
1324
1325 /**
1326 * <B>MUST BE ON SWING THREAD</B>.
1327 * Keeps model consistent with expeditee.
1328 *
1329 * Doe not use for changing a the widgets link. Must first remove, then acnhor with new link -
1330 * tracklinks links are immutable.
1331 *
1332 * @param virtualFilename
1333 * Must not be null.
1334 *
1335 * @param parentFrameName
1336 * Must not be null.
1337 *
1338 * @param newInitiationTime
1339 * In milliseconds.Relative - i.e. can be negative
1340 *
1341 * @param absoluteLinkedFrame
1342 * Must not be null. Must be a valid framename (absolute).
1343 *
1344 * @param name
1345 * The name given to the linked track. Can be null if there is no name...
1346 *
1347 * @param ypos
1348 * The Y-pixel position of the track.
1349 *
1350 */
1351 public void onLinkedTrackWidgetAnchored(
1352 String virtualFilename, String parentFrameName,
1353 long newInitiationTime, String absoluteLinkedFrame,
1354 String name, int ypos) {
1355
1356 boolean doNotify = false;
1357
1358 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1359
1360 assert(virtualFilename != null);
1361 assert(parentFrameName != null);
1362 assert(absoluteLinkedFrame != null);
1363 assert(FrameIO.isValidFrameName(absoluteLinkedFrame));
1364
1365 // Locate parent frame
1366 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1367
1368 if (odframe != null) { // is loaded?
1369
1370 LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
1371
1372 if (linkInf != null) {
1373
1374 // Update the initation time
1375 linkInf.setInitiationTime(newInitiationTime);
1376
1377 // The link should be consistant - check for miss-use of procedure call
1378 assert(linkInf.getLinkedFrame().getFrameName().equalsIgnoreCase(absoluteLinkedFrame));
1379
1380 } else { // if there is no model but overdub frame is in memory - then create new model for this track.
1381
1382 updateLater(new LinkedTrackUpdate(
1383 newInitiationTime, absoluteLinkedFrame,
1384 virtualFilename, parentFrameName, name, ypos));
1385
1386 }
1387
1388 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1389 cancelFetch = doNotify = true;
1390 // It will die in its own time - and will always be cancelled because it locks
1391 // the current locked object.
1392 }
1393
1394 }
1395
1396 // Notify observers.
1397 if (doNotify)
1398 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_ADDED, virtualFilename));
1399
1400 }
1401
1402 /**
1403 * <B>MUST BE ON SWING THREAD</B>.
1404 * Keeps model consistent with expeditee.
1405 *
1406 * Note: Invoke when picked up - not when a frame changes... also if it
1407 * is removed due to XRaymode - in that case the whole model will be released.
1408 *
1409 * <b>Also</b> invoke if the track tracks link has changed.
1410 *
1411 * @param virtualFilename
1412 * Must not be null.
1413 *
1414 * @param parentFrameName
1415 * Can be null. If given will be faster.
1416 *
1417 */
1418 public void onLinkedTrackWidgetRemoved(String virtualFilename, String parentFrameName) {
1419
1420 boolean doNotify = false;
1421
1422 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1423
1424
1425 if (DisplayController.isXRayMode()) { // discard whole model
1426
1427 // Neccessary because if the user goes into xray then moves to a new frame
1428 // then it wil screw everything up. Also note they can delete the text source items
1429 // in xray mode.
1430 // This basically will cause short load times to occur again...
1431 allOverdubbedFrames.clear();
1432 graphRoots.clear();
1433
1434
1435 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1436 cancelFetch = doNotify = true;
1437 // It will die in its own time - and will always be cancelled because it locks
1438 // the current locked object.
1439 } else {
1440
1441 // Locate parent frame
1442 OverdubbedFrame odframe = null;
1443
1444 if (parentFrameName != null) {
1445 odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1446 } else {
1447 for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
1448 if (odf.containsLinkedTrack(virtualFilename)) {
1449 odframe = odf;
1450 break;
1451 }
1452 }
1453 }
1454
1455 if (odframe != null) {
1456 LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
1457
1458 if (linkInf != null) {
1459 boolean didRemove = odframe.removeLinkedTrack(linkInf);
1460 assert(didRemove);
1461
1462 // Track links are the actual links in the directed graph. THus if they are
1463 // removed then the graph must be checked for creating new root nodes.
1464 // That is, the removed link may have isolated a node (or group of nodes)
1465 // for which must be reachable via their own start state...
1466 boolean isReachable = false;
1467 for (OverdubbedFrame existingRoot : graphRoots) {
1468 if (existingRoot.getChild(linkInf.getLinkedFrame().getFrameName()) != null) {
1469 isReachable = true;
1470 break;
1471 }
1472
1473 }
1474
1475 // Ensure that the frame is reachable
1476 if (!isReachable) {
1477 graphRoots.add(linkInf.getLinkedFrame());
1478 }
1479
1480 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1481 cancelFetch = doNotify = true;
1482 // It will die in its own time - and will always be cancelled because it locks
1483 // the current locked object.
1484
1485 }
1486
1487
1488 }
1489
1490 }
1491
1492 }
1493
1494 // Notify observers.
1495 if (doNotify)
1496 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_REMOVED, virtualFilename));
1497 }
1498
1499 /**
1500 * @return
1501 * True if the graph model is updating.
1502 */
1503 public boolean isUpdating() {
1504 return (delayedModelUpdator != null && delayedModelUpdator.isAlive());
1505 }
1506
1507
1508 /**
1509 * Waits for updates to finish.
1510 *
1511 * <b>MUST NOT BE ON SWING THREAD - OR MAY DEADLOCK</b>
1512 *
1513 * @throws InterruptedException
1514 * if any thread has interrupted the current thread
1515 */
1516 public void waitOnUpdates() throws InterruptedException {
1517 if (delayedModelUpdator != null && delayedModelUpdator.isAlive()) {
1518 delayedModelUpdator.join();
1519 }
1520 }
1521
1522 /**
1523 * Queues an update for the consistant model to be updated later... since it may take some time.
1524 *
1525 * @param update
1526 * The update to do. Must not be null.
1527 */
1528 private void updateLater(LinkedTrackUpdate update) {
1529
1530 synchronized(updateQueue) {
1531
1532 assert(update != null);
1533
1534 // Add the update tot he queu
1535 updateQueue.add(update);
1536
1537 // Ensure that the update thread is alive
1538 if (delayedModelUpdator == null || !delayedModelUpdator.isAlive()) {
1539 delayedModelUpdator = new DelayedModelUpdator();
1540 delayedModelUpdator.start();
1541 }
1542
1543 }
1544
1545 }
1546
1547 private Queue<LinkedTrackUpdate> updateQueue = new LinkedList<LinkedTrackUpdate>(); // SHARED RESOURCE
1548
1549 /**
1550 * Used for queuing update data.
1551 *
1552 *
1553 * @author Brook Novak
1554 *
1555 */
1556 private class LinkedTrackUpdate extends LinkedTrackModelData {
1557
1558 LinkedTrackUpdate(
1559 long initiationTime,
1560 String absoluteLink,
1561 String virtualFilename,
1562 String parentFrameToAddTo,
1563 String name,
1564 int ypos) {
1565
1566 super(Mutable.createMutableLong(initiationTime), absoluteLink, name, ypos);
1567
1568 assert(virtualFilename != null);
1569 assert(parentFrameToAddTo != null);
1570 assert(FrameIO.isValidFrameName(absoluteLink));
1571
1572 this.virtualFilename = virtualFilename;
1573 this.parentFrameToAddTo = parentFrameToAddTo;
1574 }
1575
1576 String virtualFilename;
1577 String parentFrameToAddTo;
1578
1579 }
1580
1581 /**
1582 *
1583 * @author Brook Novak
1584 *
1585 */
1586 private class DelayedModelUpdator extends Thread {
1587
1588 public void run() {
1589 while (true) {
1590 LinkedTrackUpdate update;
1591 synchronized(updateQueue) {
1592 if (updateQueue.isEmpty()) return; // important: only quits when updateQueue is synched
1593 update = updateQueue.poll();
1594 }
1595
1596 assert(update != null);
1597
1598 // Keep trying the update until success or encountered loops
1599 while (true) {
1600 try {
1601
1602 OverdubbedFrame odframe = fetchGraph(update.frameLink);
1603 if (odframe == null) { // does not exist or is empty...
1604 odframe = new OverdubbedFrame(update.frameLink);
1605 }
1606
1607
1608 // add to parent only if does not already exist - also being aware of loops
1609 final LinkedTrackUpdate updateData = update;
1610 final OverdubbedFrame linkedODFrame = odframe;
1611
1612 try {
1613 EcosystemManager.getMiscManager().runOnGIOThread(new BlockingRunnable() {
1614
1615 public void execute() { // on swing thread
1616
1617 synchronized(sharedResourceLocker) {
1618
1619 try { // The fetch will be cancelled after this block - within the lock
1620
1621 OverdubbedFrame parentFrame = allOverdubbedFrames.get(updateData.parentFrameToAddTo.toLowerCase());
1622 if (parentFrame == null) {
1623 // No need to both adding - because model does not even reference this new link...
1624 // must have cleared while updating... e.g. user could have switched into xray mode.
1625 return;
1626 }
1627
1628 assert(updateData.initiationTime != null);
1629
1630 // Create the linked track instance
1631 LinkedTracksGraphNode linktInf = new LinkedTracksGraphNode(
1632 updateData.initiationTime.value,
1633 linkedODFrame,
1634 updateData.virtualFilename,
1635 updateData.name,
1636 updateData.ypos);
1637
1638 // Check that the graph will be loop free
1639 parentFrame.addLinkedTrack(linktInf); // ADDING TEMPORARILY
1640
1641 // Check from parent frame
1642 Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
1643 if (!isLoopFree(parentFrame, loopTrace)) {
1644 parentFrame.removeLinkedTrack(linktInf);
1645 // Ignore link - has loop
1646 return;
1647 }
1648
1649 // Check for all existing graph roots
1650 for (OverdubbedFrame existingRoot : graphRoots) {
1651 loopTrace.clear();
1652 if (!isLoopFree(existingRoot, loopTrace)) {
1653 parentFrame.removeLinkedTrack(linktInf);
1654 // Ignore link - has loop
1655 return;
1656 }
1657 }
1658
1659 // Otherwise leave the link in its place
1660
1661 // Ensure that the linked frame is in the allOverdubbedFrames set
1662 allOverdubbedFrames.put(linkedODFrame.getFrameName().toLowerCase(), linkedODFrame);
1663
1664 } finally { // IMPORTANT: CANCEL WHILE LOCKED
1665
1666 // Cancel any fetches
1667 cancelFetch = true;
1668 }
1669
1670 } // release lock
1671
1672
1673 }
1674 });
1675 } catch (Exception e) {
1676 e.printStackTrace();
1677 assert(false);
1678 }
1679
1680 // Done with this update...
1681 break;
1682
1683
1684 } catch (InterruptedException e) { // Canceled
1685 // Consume and retry
1686
1687 } catch (TrackGraphLoopException e) { // bad link
1688 // Consume - since is fine - the model will just ignore the link
1689 break;
1690 }
1691 } // retry fetch
1692
1693 } // proccess next update
1694
1695 } // finished updating
1696 }
1697
1698}
Note: See TracBrowser for help on using the repository browser.