Skip to content

Commit

Permalink
Merge pull request #201 from DropSnorz/feat/file-stats
Browse files Browse the repository at this point in the history
Directory metrics and file statistics
  • Loading branch information
DropSnorz authored Aug 16, 2023
2 parents 11f7aba + 61c3a3f commit 5daa508
Show file tree
Hide file tree
Showing 13 changed files with 543 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@

package com.owlplug.core.components;

import com.owlplug.core.dao.FileStatDAO;
import com.owlplug.core.dao.PluginDAO;
import com.owlplug.core.dao.PluginFootprintDAO;
import com.owlplug.core.dao.SymlinkDAO;
import com.owlplug.core.model.Plugin;
import com.owlplug.core.services.NativeHostService;
import com.owlplug.core.tasks.FileSyncTask;
import com.owlplug.core.tasks.PluginRemoveTask;
import com.owlplug.core.tasks.PluginSyncTask;
import com.owlplug.core.tasks.SimpleEventListener;
import com.owlplug.core.tasks.TaskExecutionContext;
import com.owlplug.core.tasks.plugins.discovery.PluginSyncTaskParameters;
import com.owlplug.core.utils.FileUtils;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

Expand All @@ -49,6 +53,9 @@ public class CoreTaskFactory extends BaseTaskFactory {
@Autowired
private NativeHostService nativeHostService;

@Autowired
private FileStatDAO fileStatDAO;


private ArrayList<SimpleEventListener> syncPluginsListeners = new ArrayList<>();

Expand Down Expand Up @@ -89,17 +96,51 @@ public TaskExecutionContext createPluginSyncTask(String directoryScope) {
parameters.setDirectoryScope(FileUtils.convertPath(directoryScope));
}

PluginSyncTask task = new PluginSyncTask(parameters,
PluginSyncTask task = new PluginSyncTask(parameters,
pluginDAO,
pluginFootprintDAO,
symlinkDAO,
nativeHostService);

task.setOnSucceeded(e -> {
notifyListeners(syncPluginsListeners);
if (directoryScope != null) {
createFileStatSyncTask(directoryScope).scheduleNow();
} else {
createFileStatSyncTask().scheduleNow();
}
});
return create(task);
}

public TaskExecutionContext createFileStatSyncTask() {
Set<String> directorySet = new TreeSet<>();

if (prefs.getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false)) {
directorySet.add(prefs.get(ApplicationDefaults.VST_DIRECTORY_KEY, ""));
directorySet.addAll(prefs.getList(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY));
}
if (prefs.getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false)) {
directorySet.add(prefs.get(ApplicationDefaults.VST3_DIRECTORY_KEY, ""));
directorySet.addAll(prefs.getList(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY));
}
if (prefs.getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false)) {
directorySet.add(prefs.get(ApplicationDefaults.AU_DIRECTORY_KEY, ""));
directorySet.addAll(prefs.getList(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY));
}
if (prefs.getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false)) {
directorySet.add(prefs.get(ApplicationDefaults.LV2_DIRECTORY_KEY, ""));
directorySet.addAll(prefs.getList(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY));
}
FileSyncTask task = new FileSyncTask(fileStatDAO, directorySet.stream().toList());

return create(task);
}

public TaskExecutionContext createFileStatSyncTask(String directoryScope) {
FileSyncTask task = new FileSyncTask(fileStatDAO, directoryScope);
return create(task);
}


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,28 @@
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXListView;
import com.owlplug.core.components.CoreTaskFactory;
import com.owlplug.core.dao.FileStatDAO;
import com.owlplug.core.model.FileStat;
import com.owlplug.core.model.Plugin;
import com.owlplug.core.model.PluginDirectory;
import com.owlplug.core.tasks.DirectoryRemoveTask;
import com.owlplug.core.ui.DoughnutChart;
import com.owlplug.core.ui.PluginListCellFactory;
import com.owlplug.core.utils.FileUtils;
import com.owlplug.core.utils.PlatformUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

Expand All @@ -39,15 +53,23 @@ public class DirectoryInfoController extends BaseController {

@Autowired
private CoreTaskFactory taskFactory;
@Autowired
private FileStatDAO fileStatDAO;

@FXML
private Label directoryPathLabel;
@FXML
private Label directoryMetricsLabel;
@FXML
private JFXListView<Plugin> pluginDirectoryListView;
@FXML
private JFXButton openDirectoryButton;
@FXML
private JFXButton deleteDirectoryButton;
@FXML
private VBox pieChartContainer;

private PieChart pieChart;

private PluginDirectory pluginDirectory;

Expand All @@ -56,7 +78,6 @@ public class DirectoryInfoController extends BaseController {
*/
public void initialize() {

openDirectoryButton.setGraphic(new ImageView(this.getApplicationDefaults().directoryImage));
openDirectoryButton.setOnAction(e -> {
PlatformUtils.openDirectoryExplorer(pluginDirectory.getPath());
});
Expand All @@ -82,20 +103,96 @@ public void initialize() {
dialog.close();
taskFactory.create(new DirectoryRemoveTask(pluginDirectory))
.setOnSucceeded(x -> taskFactory.createPluginSyncTask(pluginDirectory.getPath()).schedule())
.schedule();
.schedule();
});
removeButton.getStyleClass().add("button-danger");

layout.setActions(removeButton, cancelButton);
dialog.setContent(layout);
dialog.show();
});

pieChart = new DoughnutChart() {
@Override
protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
if (getLabelsVisible()) {
getData().forEach(d -> {
Optional<Node> opTextNode = this.lookupAll(".chart-pie-label").stream().filter(
n -> n instanceof Text && ((Text) n).getText().equals(d.getName())).findAny();
if (opTextNode.isPresent()) {
String label = ellipsisString(d.getName(), 15, 3)
+ " - " + FileUtils.humanReadableByteCount((long) d.getPieValue(), true);
((Text) opTextNode.get()).setText(label);
}
});
}
super.layoutChartChildren(top, left, contentWidth, contentHeight);
}
};

pieChartContainer.getChildren().add(pieChart);
VBox.setVgrow(pieChart, Priority.ALWAYS);

}

public void setPluginDirectory(PluginDirectory pluginDirectory) {
this.pluginDirectory = pluginDirectory;
directoryPathLabel.setText(pluginDirectory.getPath());
pluginDirectoryListView.getItems().setAll(pluginDirectory.getPluginList());


String path = pluginDirectory.getPath();
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}

List<String> directoryMetrics = new ArrayList<>();
Optional<FileStat> directoryStat = fileStatDAO.findByPath(path);
directoryStat.ifPresent(fileStat -> directoryMetrics.add(
FileUtils.humanReadableByteCount(fileStat.getLength(), true)));
directoryMetrics.add(pluginDirectory.getPluginList().size() + " plugin(s)");

List<FileStat> fileStats = fileStatDAO.findByParentPathOrderByLengthDesc(path);
if (fileStats.size() > 0) {
directoryMetrics.add(fileStats.size() + " file(s)");
}

pieChart.setData(createStatChartBuckets(fileStats));
pieChart.layout();

directoryMetricsLabel.setText(String.join(" | ", directoryMetrics));

}

private ObservableList<PieChart.Data> createStatChartBuckets(List<FileStat> fileStats) {
ObservableList<PieChart.Data> chartData = FXCollections.observableArrayList();
int i = 0;
int maxBucket = 7;
while (i < fileStats.size() && i < maxBucket) {
chartData.add(new PieChart.Data(fileStats.get(i).getName(), fileStats.get(i).getLength()));
i = i + 1;
}

if (i < fileStats.size()) {
long groupLength = 0;
while (i < fileStats.size()) {
groupLength += fileStats.get(i).getLength();
i = i + 1;
}
chartData.add(new PieChart.Data("Others", groupLength));
}

return chartData;
}

private String ellipsisString(String input, int maxLength, int clearEndLength) {
if (input == null || input.length() <= maxLength
|| clearEndLength >= maxLength) {
return input;
} else {
String truncatedString = input.substring(0, maxLength - clearEndLength);
return truncatedString + "..." + input.substring(input.length() - clearEndLength);
}
}

}
36 changes: 36 additions & 0 deletions owlplug-client/src/main/java/com/owlplug/core/dao/FileStatDAO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* OwlPlug
* Copyright (C) 2021 Arthur <[email protected]>
*
* This file is part of OwlPlug.
*
* OwlPlug is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3
* as published by the Free Software Foundation.
*
* OwlPlug is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OwlPlug. If not, see <https://www.gnu.org/licenses/>.
*/

package com.owlplug.core.dao;

import com.owlplug.core.model.FileStat;
import jakarta.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;

public interface FileStatDAO extends CrudRepository<FileStat, Long> {

Optional<FileStat> findByPath(String path);

List<FileStat> findByParentPathOrderByLengthDesc(String parentPath);

@Transactional
List<FileStat> deleteByPath(String path);

}
105 changes: 105 additions & 0 deletions owlplug-client/src/main/java/com/owlplug/core/model/FileStat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* OwlPlug
* Copyright (C) 2021 Arthur <[email protected]>
*
* This file is part of OwlPlug.
*
* OwlPlug is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3
* as published by the Free Software Foundation.
*
* OwlPlug is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OwlPlug. If not, see <https://www.gnu.org/licenses/>.
*/

package com.owlplug.core.model;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.HashSet;
import java.util.Set;

@Entity
public class FileStat {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;

private String path;

@ManyToOne
@JoinColumn(name = "parent_id")
private FileStat parent;

private String parentPath;
private long length;

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private Set<FileStat> childs = new HashSet<>();

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public FileStat getParent() {
return parent;
}

public void setParent(FileStat parent) {
this.parent = parent;
}

public String getParentPath() {
return parentPath;
}

public void setParentPath(String parentPath) {
this.parentPath = parentPath;
}

public long getLength() {
return length;
}

public void setLength(long length) {
this.length = length;
}

public Set<FileStat> getChilds() {
return childs;
}

}
Loading

0 comments on commit 5daa508

Please sign in to comment.