DefaultAudioExplorerPresentationControl.java
- /*
- * *********************************************************************************************************************
- *
- * blueMarine II: Semantic Media Centre
- * http://tidalwave.it/projects/bluemarine2
- *
- * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
- *
- * *********************************************************************************************************************
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- *
- * *********************************************************************************************************************
- *
- * git clone https://bitbucket.org/tidalwave/bluemarine2-src
- * git clone https://github.com/tidalwave-it/bluemarine2-src
- *
- * *********************************************************************************************************************
- */
- package it.tidalwave.bluemarine2.ui.audio.explorer.impl;
- import javax.annotation.Nonnull;
- import javax.annotation.PostConstruct;
- import javax.inject.Inject;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Optional;
- import java.util.Stack;
- import java.util.concurrent.atomic.AtomicReference;
- import java.net.URL;
- import it.tidalwave.role.ui.Displayable;
- import it.tidalwave.util.annotation.VisibleForTesting;
- import javafx.application.Platform;
- import it.tidalwave.dci.annotation.DciContext;
- import it.tidalwave.util.Finder;
- import it.tidalwave.role.ui.PresentationModel;
- import it.tidalwave.role.ui.UserAction;
- import it.tidalwave.messagebus.MessageBus;
- import it.tidalwave.messagebus.annotation.ListensTo;
- import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
- import it.tidalwave.bluemarine2.model.role.EntityBrowser;
- import it.tidalwave.bluemarine2.model.spi.Entity;
- import it.tidalwave.bluemarine2.downloader.DownloadComplete;
- import it.tidalwave.bluemarine2.downloader.DownloadRequest;
- import it.tidalwave.bluemarine2.ui.commons.OpenAudioExplorerRequest;
- import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
- import it.tidalwave.bluemarine2.ui.commons.OnActivate;
- import it.tidalwave.bluemarine2.ui.audio.explorer.AudioExplorerPresentation;
- import lombok.AllArgsConstructor;
- import lombok.Getter;
- import lombok.ToString;
- import lombok.extern.slf4j.Slf4j;
- import static java.util.stream.Collectors.*;
- import static java.util.stream.Stream.*;
- import static it.tidalwave.role.ui.Displayable._Displayable_;
- import static it.tidalwave.role.SimpleComposite._SimpleComposite_;
- import static it.tidalwave.role.ui.spi.PresentationModelCollectors.*;
- import static it.tidalwave.bluemarine2.model.spi.PathAwareEntity._PathAwareEntity_;
- /***********************************************************************************************************************
- *
- * The Control of the {@link AudioExplorerPresentation}.
- *
- * @stereotype Control
- *
- * @author Fabrizio Giudici
- *
- **********************************************************************************************************************/
- @SimpleMessageSubscriber @DciContext @Slf4j
- public class DefaultAudioExplorerPresentationControl implements AudioExplorerPresentationControlSpi
- {
- @AllArgsConstructor @Getter @ToString
- private static class FolderAndMemento
- {
- @Nonnull
- private final Entity folder;
- @Nonnull
- private final Optional<Object> memento;
- }
- @Inject
- private AudioExplorerPresentation presentation;
- @Inject
- private MessageBus messageBus;
- @Inject
- private List<EntityBrowser> browsers;
- private Entity currentFolder;
- private final Stack<FolderAndMemento> navigationStack = new Stack<>();
- private final AudioExplorerPresentation.Properties properties = new AudioExplorerPresentation.Properties();
- private final UserAction navigateUpAction = UserAction.of(this::navigateUp);
- private final AtomicReference<Optional<URL>> currentCoverArtUrl = new AtomicReference<>(Optional.empty());
- @Getter
- private final List<Entity> mediaItems = new ArrayList<>();
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @PostConstruct
- @VisibleForTesting void initialize()
- {
- presentation.bind(properties, navigateUpAction);
- }
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @VisibleForTesting void onOpenAudioExplorerRequest (@ListensTo @Nonnull final OpenAudioExplorerRequest request)
- {
- log.info("onOpenAudioExplorerRequest({})", request);
- presentation.showUp(this);
- populateBrowsers();
- }
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @VisibleForTesting void onDownloadComplete (@ListensTo @Nonnull final DownloadComplete notification)
- {
- log.info("onDownloadComplete({})", notification);
- if (currentCoverArtUrl.get().map(url -> url.equals(notification.getUrl())).orElse(false))
- {
- if (notification.getStatusCode() == 200) // FIXME
- {
- presentation.setCoverArt(Optional.of(notification.getCachedUri()));
- }
- }
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- @OnActivate
- @VisibleForTesting void onActivate()
- {
- presentation.focusOnMediaItems();
- }
- /*******************************************************************************************************************
- *
- * Deactivation is disabled (and acts as navigateUpAction) when the stack is not empty.
- *
- ******************************************************************************************************************/
- @OnDeactivate
- @VisibleForTesting OnDeactivate.Result onDeactivate()
- {
- log.debug("onDeactivate()");
- if (navigationStack.isEmpty())
- {
- return OnDeactivate.Result.PROCEED;
- }
- else
- {
- navigateUp();
- return OnDeactivate.Result.IGNORE;
- }
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void selectBrowser (@Nonnull final EntityBrowser browser)
- {
- log.info("selectBrowser({})", browser);
- navigationStack.clear();
- populateItems(new FolderAndMemento(browser.getRoot(), Optional.empty()));
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void navigateTo (@Nonnull final Entity newMediaFolder)
- {
- log.debug("navigateTo({})", newMediaFolder);
- navigationStack.push(new FolderAndMemento(currentFolder, Optional.of(presentation.getMemento())));
- populateItems(new FolderAndMemento(newMediaFolder, Optional.empty()));
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void renderDetails (@Nonnull final String details)
- {
- presentation.renderDetails(details);
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void clearDetails()
- {
- presentation.setCoverArt(Optional.empty());
- presentation.renderDetails("");
- }
- /*******************************************************************************************************************
- *
- * {@inheritDoc}
- *
- ******************************************************************************************************************/
- @Override
- public void requestCoverArt (@Nonnull final Optional<URL> optionalCoverArtUrl)
- {
- log.debug("requestCoverArt({})", optionalCoverArtUrl);
- currentCoverArtUrl.set(optionalCoverArtUrl);
- optionalCoverArtUrl.ifPresent(url -> messageBus.publish(new DownloadRequest(url)));
- }
- /*******************************************************************************************************************
- *
- * Navigates up to the parent folder.
- *
- ******************************************************************************************************************/
- private void navigateUp()
- {
- log.debug("navigateUp()");
- populateItems(navigationStack.pop());
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void populateBrowsers()
- {
- log.debug("populateBrowsers()");
- // FIXME: in this case role injection doesn't work because browsers are pre-instantiated by Spring and not
- // in this context.
- // contextManager.runWithContext(this, new SimpleTask()
- // {
- // @Override
- // public Void run()
- // {
- // final PresentationModel pm = browsers.stream() // natively sorted by @OrderBy
- // .map(o -> o.as(_Presentable_).createPresentationModel())
- // .collect(toCompositePresentationModel());
- // presentation.populateBrowsers(pm);
- // selectBrowser(browsers.get(0));
- // return null;
- // }
- // });
- final PresentationModel pm = toCompositePresentationModel(browsers, EntityBrowserUserActionProvider::new);
- presentation.populateBrowsers(pm);
- selectBrowser(browsers.get(0));
- }
- /*******************************************************************************************************************
- *
- * Populates the presentation with the contents of a folder and selects an item.
- *
- * @param folderAndMemento the folder and the presentation memento
- *
- ******************************************************************************************************************/
- private void populateItems (@Nonnull final FolderAndMemento folderAndMemento)
- {
- log.debug("populateItems({})", folderAndMemento);
- this.currentFolder = folderAndMemento.getFolder();
- // FIXME: shouldn't deal with JavaFX threads here
- Platform.runLater(() -> navigateUpAction.enabled().set(!navigationStack.isEmpty()));
- Platform.runLater(() -> properties.folderNameProperty().setValue(getCurrentPathLabel()));
- final Finder<? extends Entity> finder = currentFolder.as(_SimpleComposite_).findChildren().withContext(this);
- mediaItems.clear();
- // mediaItems.addAll(finder.stream().filter(i -> i instanceof MediaItem).map(i -> (MediaItem)i).collect(toList
- // ()));
- mediaItems.addAll(finder.results());
- // Needs the cast for overloading ambiguity in the method signature
- final PresentationModel pm = toCompositePresentationModel(mediaItems);
- presentation.populateItems(pm, folderAndMemento.getMemento());
- }
- /*******************************************************************************************************************
- *
- * Computes the label describing the current navigation path.
- *
- ******************************************************************************************************************/
- @Nonnull
- private String getCurrentPathLabel()
- {
- return concat(navigationStack.stream().map(FolderAndMemento::getFolder), of(currentFolder))
- .filter(i -> i.maybeAs(_PathAwareEntity_).map(p -> p.getParent().isPresent()).orElse(true))
- .filter(i -> i.maybeAs(_Displayable_).isPresent())
- .map(i -> i.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse("???"))
- .collect(joining(" / "));
- }
- }