source: trunk/src_apollo/org/apollo/audio/structure/AudioStructureModel.java@ 318

Last change on this file since 318 was 318, checked in by bjn8, 16 years ago

Refactored a class name and extended recorder widgets to have a perminant lifetime option (for optimum idea capturing!)

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