/* * Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com] * 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. */ package de.ks.file; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.ThreadFactoryBuilder; import de.ks.activity.ActivityLoadFinishedEvent; import de.ks.activity.context.ActivityStore; import de.ks.activity.initialization.DatasourceCallback; import de.ks.idnadrev.entity.FileContainer; import de.ks.idnadrev.entity.FileReference; import de.ks.persistence.PersistentWork; import de.ks.text.image.GlobalImageProvider; import de.ks.text.image.ImageData; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; import javafx.stage.FileChooser; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FileViewController implements Initializable, DatasourceCallback<FileContainer<?>> { private static final Logger log = LoggerFactory.getLogger(FileViewController.class); protected final ObservableList<File> files = FXCollections.observableArrayList(); protected final java.awt.Desktop desktop = java.awt.Desktop.getDesktop(); protected final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("awt.Desktop-%d").build()); @FXML protected Button edit; @FXML protected Button open; @FXML protected Button openFolder; @FXML protected Button addNewFile; @FXML protected Button removeFile; @FXML protected Label fileNameLabel; @FXML protected Label folderName; @FXML protected ListView<File> fileList; @Inject protected ActivityStore store; @Inject protected FileStore fileStore; @Inject protected GlobalImageProvider imageProvider; protected final Map<File, CompletableFuture<FileReference>> fileReferences = new HashMap<>(); @Override public void initialize(URL location, ResourceBundle resources) { fileList.setItems(files); MultipleSelectionModel<File> selectionModel = fileList.getSelectionModel(); selectionModel.setSelectionMode(SelectionMode.MULTIPLE); ReadOnlyObjectProperty<File> selection = selectionModel.selectedItemProperty(); selection.addListener((p, o, n) -> { folderName.setText(n == null ? "" : n.getParentFile().getAbsolutePath()); fileNameLabel.setText(n == null ? "" : n.getName()); }); BooleanBinding isDirectory = Bindings.createBooleanBinding(() -> selection.get() != null && selection.get().isDirectory(), selection); edit.disableProperty().bind(isDirectory); files.addListener((ListChangeListener<File>) change -> { files.forEach(file -> { if (!fileReferences.containsKey(file) && file.exists() && !file.isDirectory()) { fileReferences.put(file, fileStore.getReference(file)); } }); }); BooleanBinding disable = fileList.getSelectionModel().selectedItemProperty().isNull(); open.disableProperty().bind(disable); edit.disableProperty().bind(disable); openFolder.disableProperty().bind(disable); removeFile.disableProperty().bind(disable); } public void addFiles(List<File> additionalFiles) { additionalFiles.removeAll(files); additionalFiles.forEach(this::addPossibleImage); log.info("Adding addtional files {}", additionalFiles); files.addAll(additionalFiles); if (!additionalFiles.isEmpty()) { Collections.sort(files); Collections.sort(additionalFiles); File lastFile = additionalFiles.get(additionalFiles.size() - 1); fileList.scrollTo(lastFile); fileList.getSelectionModel().clearSelection(); fileList.getSelectionModel().select(lastFile); } } protected void addPossibleImage(File file) { if (file == null) { return; } try { String contentType = Files.probeContentType(file.toPath()); if (contentType != null && contentType.contains("image")) { imageProvider.addImage(new ImageData(file.getName(), file.getPath())); } } catch (IOException e) { // } } public ObservableList<File> getFiles() { return files; } public ListView<File> getFileList() { return fileList; } @FXML void open(ActionEvent event) { ObservableList<File> items = fileList.getSelectionModel().getSelectedItems(); for (File item : items) { executor.submit(() -> { try { log.info("Opening {}", item); desktop.open(item); } catch (IOException e) { log.error("Could not open {}", item, e); } }); } } @FXML void edit(ActionEvent event) { ObservableList<File> items = fileList.getSelectionModel().getSelectedItems(); for (File item : items) { executor.submit(() -> { try { log.info("Editing {}", item); desktop.edit(item); } catch (IOException e) { log.error("Could not open {}", item, e); } }); } } @FXML void openFolder(ActionEvent event) { TreeSet<File> files = new TreeSet<>(); ObservableList<File> items = fileList.getSelectionModel().getSelectedItems(); for (File item : items) { if (item.isDirectory()) { files.add(item); } else { files.add(item.getParentFile()); } } for (File file : files) { executor.submit(() -> { try { log.info("Opening {}", file); desktop.open(file); } catch (IOException e) { log.error("Could not open {}", file, e); } }); } } @FXML public void removeFile(ActionEvent event) { ObservableList<File> selectedItems = fileList.getSelectionModel().getSelectedItems(); log.info("Removing files {}", selectedItems); selectedItems.forEach(f -> files.remove(f)); } @FXML void addNewFile(ActionEvent event) { FileChooser fileChooser = new FileChooser(); File file = fileChooser.showOpenDialog(edit.getScene().getWindow()); if (file != null) { addFiles(Arrays.asList(file)); } } @Subscribe public void onRefresh(ActivityLoadFinishedEvent event) { log.debug("Clearing files"); files.clear(); fileReferences.clear(); event.<FileContainer<?>>getModel().getFiles().forEach(f -> { File file = fileStore.getFile(f); fileReferences.put(file, CompletableFuture.completedFuture(f)); addPossibleImage(file); files.add(file); }); } @Override public void duringLoad(FileContainer<?> model) { model.getFiles().forEach(f -> f.getName()); } @Override public void duringSave(FileContainer<?> model) { fileReferences.keySet().retainAll(files); if (this.fileReferences.isEmpty()) { log.info("No files to save for {}", model); } this.fileReferences.entrySet().forEach(entry -> { try { File file = entry.getKey(); CompletableFuture<FileReference> cf = entry.getValue(); FileReference fileReference = cf.get(); model.getFiles().remove(fileReference); model.addFileReference(PersistentWork.reload(fileReference));//ensure it is saved if (fileReference.getId() > 0) { return; } fileStore.scheduleCopy(fileReference, file); String search = "file:///" + file.getAbsolutePath(); String replacement = FileReference.FILESTORE_VAR + fileReference.getMd5Sum() + File.separator + file.getName(); String newDescription = StringUtils.replace(model.getDescription(), search, replacement); model.setDescription(newDescription); log.info("Adding file reference {}", fileReference); PersistentWork.persist(fileReference); } catch (InterruptedException | ExecutionException e) { log.error("Could not get fileReference for file {}", entry.getKey()); throw new RuntimeException(e); } }); } }