DefaultAudioExplorerPresentationControl.java

  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. import javax.annotation.Nonnull;
  29. import javax.annotation.PostConstruct;
  30. import javax.inject.Inject;
  31. import java.util.ArrayList;
  32. import java.util.List;
  33. import java.util.Optional;
  34. import java.util.Stack;
  35. import java.util.concurrent.atomic.AtomicReference;
  36. import java.net.URL;
  37. import it.tidalwave.role.ui.Displayable;
  38. import it.tidalwave.util.annotation.VisibleForTesting;
  39. import javafx.application.Platform;
  40. import it.tidalwave.dci.annotation.DciContext;
  41. import it.tidalwave.util.Finder;
  42. import it.tidalwave.role.ui.PresentationModel;
  43. import it.tidalwave.role.ui.UserAction;
  44. import it.tidalwave.messagebus.MessageBus;
  45. import it.tidalwave.messagebus.annotation.ListensTo;
  46. import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
  47. import it.tidalwave.bluemarine2.model.role.EntityBrowser;
  48. import it.tidalwave.bluemarine2.model.spi.Entity;
  49. import it.tidalwave.bluemarine2.downloader.DownloadComplete;
  50. import it.tidalwave.bluemarine2.downloader.DownloadRequest;
  51. import it.tidalwave.bluemarine2.ui.commons.OpenAudioExplorerRequest;
  52. import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
  53. import it.tidalwave.bluemarine2.ui.commons.OnActivate;
  54. import it.tidalwave.bluemarine2.ui.audio.explorer.AudioExplorerPresentation;
  55. import lombok.AllArgsConstructor;
  56. import lombok.Getter;
  57. import lombok.ToString;
  58. import lombok.extern.slf4j.Slf4j;
  59. import static java.util.stream.Collectors.*;
  60. import static java.util.stream.Stream.*;
  61. import static it.tidalwave.role.ui.Displayable._Displayable_;
  62. import static it.tidalwave.role.SimpleComposite._SimpleComposite_;
  63. import static it.tidalwave.role.ui.spi.PresentationModelCollectors.*;
  64. import static it.tidalwave.bluemarine2.model.spi.PathAwareEntity._PathAwareEntity_;

  65. /***********************************************************************************************************************
  66.  *
  67.  * The Control of the {@link AudioExplorerPresentation}.
  68.  *
  69.  * @stereotype  Control
  70.  *
  71.  * @author  Fabrizio Giudici
  72.  *
  73.  **********************************************************************************************************************/
  74. @SimpleMessageSubscriber @DciContext @Slf4j
  75. public class DefaultAudioExplorerPresentationControl implements AudioExplorerPresentationControlSpi
  76.   {
  77.     @AllArgsConstructor @Getter @ToString
  78.     private static class FolderAndMemento
  79.       {
  80.         @Nonnull
  81.         private final Entity folder;

  82.         @Nonnull
  83.         private final Optional<Object> memento;
  84.       }

  85.     @Inject
  86.     private AudioExplorerPresentation presentation;

  87.     @Inject
  88.     private MessageBus messageBus;

  89.     @Inject
  90.     private List<EntityBrowser> browsers;

  91.     private Entity currentFolder;

  92.     private final Stack<FolderAndMemento> navigationStack = new Stack<>();

  93.     private final AudioExplorerPresentation.Properties properties = new AudioExplorerPresentation.Properties();

  94.     private final UserAction navigateUpAction = UserAction.of(this::navigateUp);

  95.     private final AtomicReference<Optional<URL>> currentCoverArtUrl = new AtomicReference<>(Optional.empty());

  96.     @Getter
  97.     private final List<Entity> mediaItems = new ArrayList<>();

  98.     /*******************************************************************************************************************
  99.      *
  100.      *
  101.      ******************************************************************************************************************/
  102.     @PostConstruct
  103.     @VisibleForTesting void initialize()
  104.       {
  105.         presentation.bind(properties, navigateUpAction);
  106.       }

  107.     /*******************************************************************************************************************
  108.      *
  109.      *
  110.      ******************************************************************************************************************/
  111.     @VisibleForTesting void onOpenAudioExplorerRequest (@ListensTo @Nonnull final OpenAudioExplorerRequest request)
  112.       {
  113.         log.info("onOpenAudioExplorerRequest({})", request);
  114.         presentation.showUp(this);
  115.         populateBrowsers();
  116.       }

  117.     /*******************************************************************************************************************
  118.      *
  119.      *
  120.      ******************************************************************************************************************/
  121.     @VisibleForTesting void onDownloadComplete (@ListensTo @Nonnull final DownloadComplete notification)
  122.       {
  123.         log.info("onDownloadComplete({})", notification);

  124.         if (currentCoverArtUrl.get().map(url -> url.equals(notification.getUrl())).orElse(false))
  125.           {
  126.             if (notification.getStatusCode() == 200) // FIXME
  127.               {
  128.                 presentation.setCoverArt(Optional.of(notification.getCachedUri()));
  129.               }
  130.           }
  131.       }

  132.     /*******************************************************************************************************************
  133.      *
  134.      *
  135.      *
  136.      ******************************************************************************************************************/
  137.     @OnActivate
  138.     @VisibleForTesting void onActivate()
  139.       {
  140.         presentation.focusOnMediaItems();
  141.       }

  142.     /*******************************************************************************************************************
  143.      *
  144.      * Deactivation is disabled (and acts as navigateUpAction) when the stack is not empty.
  145.      *
  146.      ******************************************************************************************************************/
  147.     @OnDeactivate
  148.     @VisibleForTesting OnDeactivate.Result onDeactivate()
  149.       {
  150.         log.debug("onDeactivate()");

  151.         if (navigationStack.isEmpty())
  152.           {
  153.             return OnDeactivate.Result.PROCEED;
  154.           }
  155.         else
  156.           {
  157.             navigateUp();
  158.             return OnDeactivate.Result.IGNORE;
  159.           }
  160.       }

  161.     /*******************************************************************************************************************
  162.      *
  163.      * {@inheritDoc}
  164.      *
  165.      ******************************************************************************************************************/
  166.     @Override
  167.     public void selectBrowser (@Nonnull final EntityBrowser browser)
  168.       {
  169.         log.info("selectBrowser({})", browser);
  170.         navigationStack.clear();
  171.         populateItems(new FolderAndMemento(browser.getRoot(), Optional.empty()));
  172.       }

  173.     /*******************************************************************************************************************
  174.      *
  175.      * {@inheritDoc}
  176.      *
  177.      ******************************************************************************************************************/
  178.     @Override
  179.     public void navigateTo (@Nonnull final Entity newMediaFolder)
  180.       {
  181.         log.debug("navigateTo({})", newMediaFolder);
  182.         navigationStack.push(new FolderAndMemento(currentFolder, Optional.of(presentation.getMemento())));
  183.         populateItems(new FolderAndMemento(newMediaFolder, Optional.empty()));
  184.       }

  185.     /*******************************************************************************************************************
  186.      *
  187.      * {@inheritDoc}
  188.      *
  189.      ******************************************************************************************************************/
  190.     @Override
  191.     public void renderDetails (@Nonnull final String details)
  192.       {
  193.         presentation.renderDetails(details);
  194.       }

  195.     /*******************************************************************************************************************
  196.      *
  197.      * {@inheritDoc}
  198.      *
  199.      ******************************************************************************************************************/
  200.     @Override
  201.     public void clearDetails()
  202.       {
  203.         presentation.setCoverArt(Optional.empty());
  204.         presentation.renderDetails("");
  205.       }

  206.     /*******************************************************************************************************************
  207.      *
  208.      * {@inheritDoc}
  209.      *
  210.      ******************************************************************************************************************/
  211.     @Override
  212.     public void requestCoverArt (@Nonnull final Optional<URL> optionalCoverArtUrl)
  213.       {
  214.         log.debug("requestCoverArt({})", optionalCoverArtUrl);
  215.         currentCoverArtUrl.set(optionalCoverArtUrl);
  216.         optionalCoverArtUrl.ifPresent(url -> messageBus.publish(new DownloadRequest(url)));
  217.       }

  218.     /*******************************************************************************************************************
  219.      *
  220.      * Navigates up to the parent folder.
  221.      *
  222.      ******************************************************************************************************************/
  223.     private void navigateUp()
  224.       {
  225.         log.debug("navigateUp()");
  226.         populateItems(navigationStack.pop());
  227.       }

  228.     /*******************************************************************************************************************
  229.      *
  230.      *
  231.      *
  232.      ******************************************************************************************************************/
  233.     private void populateBrowsers()
  234.       {
  235.         log.debug("populateBrowsers()");

  236.         // FIXME: in this case role injection doesn't work because browsers are pre-instantiated by Spring and not
  237.         // in this context.
  238. //        contextManager.runWithContext(this, new SimpleTask()
  239. //          {
  240. //            @Override
  241. //            public Void run()
  242. //              {
  243. //                final PresentationModel pm = browsers.stream() // natively sorted by @OrderBy
  244. //                                                     .map(o -> o.as(_Presentable_).createPresentationModel())
  245. //                                                     .collect(toCompositePresentationModel());
  246. //                presentation.populateBrowsers(pm);
  247. //                selectBrowser(browsers.get(0));
  248. //                return null;
  249. //              }
  250. //           });

  251.         final PresentationModel pm = toCompositePresentationModel(browsers, EntityBrowserUserActionProvider::new);
  252.         presentation.populateBrowsers(pm);
  253.         selectBrowser(browsers.get(0));
  254.       }

  255.     /*******************************************************************************************************************
  256.      *
  257.      * Populates the presentation with the contents of a folder and selects an item.
  258.      *
  259.      * @param   folderAndMemento    the folder and the presentation memento
  260.      *
  261.      ******************************************************************************************************************/
  262.     private void populateItems (@Nonnull final FolderAndMemento folderAndMemento)
  263.       {
  264.         log.debug("populateItems({})", folderAndMemento);
  265.         this.currentFolder = folderAndMemento.getFolder();
  266.         // FIXME: shouldn't deal with JavaFX threads here
  267.         Platform.runLater(() -> navigateUpAction.enabled().set(!navigationStack.isEmpty()));
  268.         Platform.runLater(() -> properties.folderNameProperty().setValue(getCurrentPathLabel()));
  269.         final Finder<? extends Entity> finder = currentFolder.as(_SimpleComposite_).findChildren().withContext(this);
  270.         mediaItems.clear();
  271.         // mediaItems.addAll(finder.stream().filter(i -> i instanceof MediaItem).map(i -> (MediaItem)i).collect(toList
  272.         // ()));
  273.         mediaItems.addAll(finder.results());
  274.         // Needs the cast for overloading ambiguity in the method signature
  275.         final PresentationModel pm = toCompositePresentationModel(mediaItems);
  276.         presentation.populateItems(pm, folderAndMemento.getMemento());
  277.       }

  278.     /*******************************************************************************************************************
  279.      *
  280.      * Computes the label describing the current navigation path.
  281.      *
  282.      ******************************************************************************************************************/
  283.     @Nonnull
  284.     private String getCurrentPathLabel()
  285.       {
  286.         return concat(navigationStack.stream().map(FolderAndMemento::getFolder), of(currentFolder))
  287.                 .filter(i -> i.maybeAs(_PathAwareEntity_).map(p -> p.getParent().isPresent()).orElse(true))
  288.                 .filter(i -> i.maybeAs(_Displayable_).isPresent())
  289.                 .map(i -> i.maybeAs(_Displayable_).map(Displayable::getDisplayName).orElse("???"))
  290.                 .collect(joining(" / "));
  291.       }
  292.   }