Skip to content

Package: DefaultAudioExplorerPresentationControl$FolderAndMemento

DefaultAudioExplorerPresentationControl$FolderAndMemento

nameinstructionbranchcomplexitylinemethod
DefaultAudioExplorerPresentationControl.FolderAndMemento(Entity, Optional)
M: 17 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getFolder()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getMemento()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
toString()
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * *********************************************************************************************************************
3: *
4: * blueMarine II: Semantic Media Centre
5: * http://tidalwave.it/projects/bluemarine2
6: *
7: * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/bluemarine2-src
23: * git clone https://github.com/tidalwave-it/bluemarine2-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.bluemarine2.ui.audio.explorer.impl;
28:
29: import javax.annotation.Nonnull;
30: import javax.annotation.PostConstruct;
31: import javax.inject.Inject;
32: import java.util.ArrayList;
33: import java.util.List;
34: import java.util.Optional;
35: import java.util.Stack;
36: import java.util.concurrent.atomic.AtomicReference;
37: import java.net.URL;
38: import javafx.application.Platform;
39: import it.tidalwave.util.Finder;
40: import it.tidalwave.util.annotation.VisibleForTesting;
41: import it.tidalwave.role.ui.Displayable;
42: import it.tidalwave.role.ui.PresentationModel;
43: import it.tidalwave.role.ui.UserAction;
44: import it.tidalwave.dci.annotation.DciContext;
45: import it.tidalwave.messagebus.MessageBus;
46: import it.tidalwave.messagebus.annotation.ListensTo;
47: import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
48: import it.tidalwave.bluemarine2.model.role.EntityBrowser;
49: import it.tidalwave.bluemarine2.model.spi.Entity;
50: import it.tidalwave.bluemarine2.downloader.DownloadComplete;
51: import it.tidalwave.bluemarine2.downloader.DownloadRequest;
52: import it.tidalwave.bluemarine2.ui.commons.OnActivate;
53: import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
54: import it.tidalwave.bluemarine2.ui.commons.OpenAudioExplorerRequest;
55: import it.tidalwave.bluemarine2.ui.audio.explorer.AudioExplorerPresentation;
56: import lombok.AllArgsConstructor;
57: import lombok.Getter;
58: import lombok.ToString;
59: import lombok.extern.slf4j.Slf4j;
60: import static java.util.stream.Collectors.*;
61: import static java.util.stream.Stream.*;
62: import static it.tidalwave.role.SimpleComposite._SimpleComposite_;
63: import static it.tidalwave.role.ui.Displayable._Displayable_;
64: import static it.tidalwave.role.ui.spi.PresentationModelCollectors.toCompositePresentationModel;
65: import static it.tidalwave.bluemarine2.model.spi.PathAwareEntity._PathAwareEntity_;
66:
67: /***********************************************************************************************************************
68: *
69: * The Control of the {@link AudioExplorerPresentation}.
70: *
71: * @stereotype Control
72: *
73: * @author Fabrizio Giudici
74: *
75: **********************************************************************************************************************/
76: @SimpleMessageSubscriber @DciContext @Slf4j
77: public class DefaultAudioExplorerPresentationControl implements AudioExplorerPresentationControlSpi
78: {
79: @AllArgsConstructor @Getter @ToString
80: private static class FolderAndMemento
81: {
82: @Nonnull
83: private final Entity folder;
84:
85: @Nonnull
86: private final Optional<Object> memento;
87: }
88:
89: @Inject
90: private AudioExplorerPresentation presentation;
91:
92: @Inject
93: private MessageBus messageBus;
94:
95: @Inject
96: private List<EntityBrowser> browsers;
97:
98: private Entity currentFolder;
99:
100: private final Stack<FolderAndMemento> navigationStack = new Stack<>();
101:
102: private final AudioExplorerPresentation.Properties properties = new AudioExplorerPresentation.Properties();
103:
104: private final UserAction navigateUpAction = UserAction.of(this::navigateUp);
105:
106: private final AtomicReference<Optional<URL>> currentCoverArtUrl = new AtomicReference<>(Optional.empty());
107:
108: @Getter
109: private final List<Entity> mediaItems = new ArrayList<>();
110:
111: /*******************************************************************************************************************
112: *
113: *
114: ******************************************************************************************************************/
115: @PostConstruct
116: @VisibleForTesting void initialize()
117: {
118: presentation.bind(properties, navigateUpAction);
119: }
120:
121: /*******************************************************************************************************************
122: *
123: *
124: ******************************************************************************************************************/
125: @VisibleForTesting void onOpenAudioExplorerRequest (@ListensTo @Nonnull final OpenAudioExplorerRequest request)
126: {
127: log.info("onOpenAudioExplorerRequest({})", request);
128: presentation.showUp(this);
129: populateBrowsers();
130: }
131:
132: /*******************************************************************************************************************
133: *
134: *
135: ******************************************************************************************************************/
136: @VisibleForTesting void onDownloadComplete (@ListensTo @Nonnull final DownloadComplete notification)
137: {
138: log.info("onDownloadComplete({})", notification);
139:
140: if (currentCoverArtUrl.get().map(url -> url.equals(notification.getUrl())).orElse(false))
141: {
142: if (notification.getStatusCode() == 200) // FIXME
143: {
144: presentation.setCoverArt(Optional.of(notification.getCachedUri()));
145: }
146: }
147: }
148:
149: /*******************************************************************************************************************
150: *
151: *
152: *
153: ******************************************************************************************************************/
154: @OnActivate
155: @VisibleForTesting void onActivate()
156: {
157: presentation.focusOnMediaItems();
158: }
159:
160: /*******************************************************************************************************************
161: *
162: * Deactivation is disabled (and acts as navigateUpAction) when the stack is not empty.
163: *
164: ******************************************************************************************************************/
165: @OnDeactivate
166: @VisibleForTesting OnDeactivate.Result onDeactivate()
167: {
168: log.debug("onDeactivate()");
169:
170: if (navigationStack.isEmpty())
171: {
172: return OnDeactivate.Result.PROCEED;
173: }
174: else
175: {
176: navigateUp();
177: return OnDeactivate.Result.IGNORE;
178: }
179: }
180:
181: /*******************************************************************************************************************
182: *
183: * {@inheritDoc}
184: *
185: ******************************************************************************************************************/
186: @Override
187: public void selectBrowser (@Nonnull final EntityBrowser browser)
188: {
189: log.info("selectBrowser({})", browser);
190: navigationStack.clear();
191: populateItems(new FolderAndMemento(browser.getRoot(), Optional.empty()));
192: }
193:
194: /*******************************************************************************************************************
195: *
196: * {@inheritDoc}
197: *
198: ******************************************************************************************************************/
199: @Override
200: public void navigateTo (@Nonnull final Entity newMediaFolder)
201: {
202: log.debug("navigateTo({})", newMediaFolder);
203: navigationStack.push(new FolderAndMemento(currentFolder, Optional.of(presentation.getMemento())));
204: populateItems(new FolderAndMemento(newMediaFolder, Optional.empty()));
205: }
206:
207: /*******************************************************************************************************************
208: *
209: * {@inheritDoc}
210: *
211: ******************************************************************************************************************/
212: @Override
213: public void renderDetails (@Nonnull final String details)
214: {
215: presentation.renderDetails(details);
216: }
217:
218: /*******************************************************************************************************************
219: *
220: * {@inheritDoc}
221: *
222: ******************************************************************************************************************/
223: @Override
224: public void clearDetails()
225: {
226: presentation.setCoverArt(Optional.empty());
227: presentation.renderDetails("");
228: }
229:
230: /*******************************************************************************************************************
231: *
232: * {@inheritDoc}
233: *
234: ******************************************************************************************************************/
235: @Override
236: public void requestCoverArt (@Nonnull final Optional<URL> optionalCoverArtUrl)
237: {
238: log.debug("requestCoverArt({})", optionalCoverArtUrl);
239: currentCoverArtUrl.set(optionalCoverArtUrl);
240: optionalCoverArtUrl.ifPresent(url -> messageBus.publish(new DownloadRequest(url)));
241: }
242:
243: /*******************************************************************************************************************
244: *
245: * Navigates up to the parent folder.
246: *
247: ******************************************************************************************************************/
248: private void navigateUp()
249: {
250: log.debug("navigateUp()");
251: populateItems(navigationStack.pop());
252: }
253:
254: /*******************************************************************************************************************
255: *
256: *
257: *
258: ******************************************************************************************************************/
259: private void populateBrowsers()
260: {
261: log.debug("populateBrowsers()");
262:
263: // FIXME: in this case role injection doesn't work because browsers are pre-instantiated by Spring and not
264: // in this context.
265: // contextManager.runWithContext(this, new SimpleTask()
266: // {
267: // @Override
268: // public Void run()
269: // {
270: // final PresentationModel pm = browsers.stream() // natively sorted by @OrderBy
271: // .map(o -> o.as(_Presentable_).createPresentationModel())
272: // .collect(toCompositePresentationModel());
273: // presentation.populateBrowsers(pm);
274: // selectBrowser(browsers.get(0));
275: // return null;
276: // }
277: // });
278:
279: final PresentationModel pm = toCompositePresentationModel(browsers, EntityBrowserUserActionProvider::new);
280: presentation.populateBrowsers(pm);
281: selectBrowser(browsers.get(0));
282: }
283:
284: /*******************************************************************************************************************
285: *
286: * Populates the presentation with the contents of a folder and selects an item.
287: *
288: * @param folderAndMemento the folder and the presentation memento
289: *
290: ******************************************************************************************************************/
291: private void populateItems (@Nonnull final FolderAndMemento folderAndMemento)
292: {
293: log.debug("populateItems({})", folderAndMemento);
294: this.currentFolder = folderAndMemento.getFolder();
295: // FIXME: shouldn't deal with JavaFX threads here
296: Platform.runLater(() -> navigateUpAction.enabled().set(!navigationStack.isEmpty()));
297: Platform.runLater(() -> properties.folderNameProperty().setValue(getCurrentPathLabel()));
298: final Finder<? extends Entity> finder = currentFolder.as(_SimpleComposite_).findChildren().withContext(this);
299: mediaItems.clear();
300: // mediaItems.addAll(finder.stream().filter(i -> i instanceof MediaItem).map(i -> (MediaItem)i).collect(toList
301: // ()));
302: mediaItems.addAll(finder.results());
303: // Needs the cast for overloading ambiguity in the method signature
304: final PresentationModel pm = toCompositePresentationModel(mediaItems);
305: presentation.populateItems(pm, folderAndMemento.getMemento());
306: }
307:
308: /*******************************************************************************************************************
309: *
310: * Computes the label describing the current navigation path.
311: *
312: ******************************************************************************************************************/
313: @Nonnull
314: private String getCurrentPathLabel()
315: {
316: return concat(navigationStack.stream().map(FolderAndMemento::getFolder), of(currentFolder))
317: .filter(i -> i.maybeAs(_PathAwareEntity_).map(p -> p.getParent().isPresent()).orElse(true))
318: .filter(i -> i.maybeAs(_Displayable_).isPresent())
319: .map(i -> i.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse("???"))
320: .collect(joining(" / "));
321: }
322: }