Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 68 additions & 18 deletions src/main/java/me/maxih/itunes_backup_explorer/ui/Dialogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import me.maxih.itunes_backup_explorer.ITunesBackupExplorer;
Expand All @@ -18,6 +27,28 @@

public class Dialogs {

private static void centerOnOwner(Stage popup) {
Stage owner = Stage.getWindows().stream()
.filter(window -> window.isFocused() && window instanceof Stage)
.map(window -> (Stage) window)
.findFirst()
.orElse(null);

Rectangle2D bounds;
if (owner != null) {
popup.initOwner(owner);
popup.initModality(Modality.WINDOW_MODAL);
bounds = Screen.getScreensForRectangle(owner.getX(), owner.getY(), owner.getWidth(), owner.getHeight()).get(0).getVisualBounds();
} else {
bounds = Screen.getPrimary().getVisualBounds();
}

popup.setX(bounds.getMinX() + (bounds.getWidth() - popup.getWidth()) / 2);
popup.setY(bounds.getMinY() + (bounds.getHeight() - popup.getHeight()) / 2);
popup.toFront();
popup.setAlwaysOnTop(true);
}

public static Optional<String> askPassword() {
Dialog<String> dialog = new Dialog<>();
dialog.setTitle("Enter the password");
Expand All @@ -26,60 +57,79 @@ public static Optional<String> askPassword() {
((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().add(ITunesBackupExplorer.APP_ICON);

PasswordField passwordField = new PasswordField();
dialog.setOnShown(event -> Platform.runLater(passwordField::requestFocus));
dialog.setOnShown(event -> {
Platform.runLater(passwordField::requestFocus);
centerOnOwner((Stage) dialog.getDialogPane().getScene().getWindow());
});

HBox content = new HBox();
content.setAlignment(Pos.CENTER_LEFT);
content.setSpacing(10);
content.getChildren().addAll(new Label("Please type in your password here:"), passwordField);
dialog.getDialogPane().setContent(content);

dialog.setResultConverter(pressedButton ->
pressedButton == ButtonType.OK ? passwordField.getText() : null);

dialog.setResultConverter(pressedButton -> pressedButton == ButtonType.OK ? passwordField.getText() : null);
return dialog.showAndWait();
}

public static Alert getAlert(Alert.AlertType type, String message, ButtonType... buttonTypes) {
public static Alert getAlert(AlertType type, String message, ButtonType... buttonTypes) {
Alert alert = new Alert(type, message, buttonTypes);
((Stage) alert.getDialogPane().getScene().getWindow()).getIcons().add(ITunesBackupExplorer.APP_ICON);
alert.setOnShown(event -> centerOnOwner((Stage) alert.getDialogPane().getScene().getWindow()));
return alert;
}

public static Optional<ButtonType> showAlert(Alert.AlertType type, String message, ButtonType... buttonTypes) {
return getAlert(type, message, buttonTypes).showAndWait();
public static Optional<ButtonType> showAlert(AlertType type, String message, ButtonType... buttonTypes) {
Alert alert = getAlert(type, message, buttonTypes);
alert.show();
return alert.showAndWait();
}

public static class ProgressAlert extends Stage {

public ProgressAlert(String title, Task<?> task, EventHandler<WindowEvent> cancelEventHandler) {
this.initModality(Modality.APPLICATION_MODAL);
this.setTitle(title);
this.setResizable(false);
this.setOnCloseRequest(cancelEventHandler);
this.getIcons().add(ITunesBackupExplorer.APP_ICON);
initModality(Modality.APPLICATION_MODAL);
setTitle(title);
setResizable(false);
setOnCloseRequest(cancelEventHandler);
getIcons().add(ITunesBackupExplorer.APP_ICON);

ProgressBar bar = new ProgressBar();
bar.setPrefSize(250, 50);
bar.setPadding(new Insets(10));
bar.progressProperty().bind(task.progressProperty());
task.runningProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue && !newValue) this.close();
if (oldValue && !newValue) close();
});

this.setScene(new Scene(bar));
Label header = new Label(title);
VBox content = new VBox(10, header, bar);
content.setPadding(new Insets(10));
content.setAlignment(Pos.CENTER);

setScene(new Scene(content));

setOnShown(event -> centerOnOwner(this));
}

public ProgressAlert(String title, Task<?> task, boolean cancellable) {
this(title, task, cancellable ? event -> task.cancel() : Event::consume);
}

public ProgressAlert(String title, Task<?> task, Runnable cancelAction) {
this(title, task, event -> cancelAction.run());
}

}

private Dialogs() {
}

}
public static void showSuccessDialog(String message) {
Alert alert = new Alert(AlertType.INFORMATION, message, ButtonType.OK);
alert.setTitle("Export successful");
alert.setHeaderText("Backup successfully exported");
((Stage) alert.getDialogPane().getScene().getWindow()).getIcons().add(ITunesBackupExplorer.APP_ICON);

alert.setOnShown(event -> centerOnOwner((Stage) alert.getDialogPane().getScene().getWindow()));
alert.showAndWait();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ public static void extractFile(BackupFile file, Window chooserOwnerWindow) {
File destination = chooser.showSaveDialog(chooserOwnerWindow);
if (destination == null) return;


try {
file.extract(destination);
} catch (IOException | BackupReadException | NotUnlockedException | UnsupportedCryptoException e) {
Dialogs.showSuccessDialog("The file was successfully exported to:\n" + destination.getAbsolutePath());
}

catch (IOException | BackupReadException | NotUnlockedException | UnsupportedCryptoException e) {
e.printStackTrace();
Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage(), ButtonType.OK);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package me.maxih.itunes_backup_explorer.ui;

import javafx.collections.FXCollections;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.DirectoryChooser;
Expand Down Expand Up @@ -30,71 +32,134 @@ public void initialize() {
TableColumn<BackupFileEntry, String> nameColumn = new TableColumn<>("Name");
TableColumn<BackupFileEntry, String> pathColumn = new TableColumn<>("Path");
TableColumn<BackupFileEntry, Number> sizeColumn = new TableColumn<>("Size");

domainColumn.setCellValueFactory(new PropertyValueFactory<>("domain"));
nameColumn.setCellValueFactory(new PropertyValueFactory<>("fileName"));
pathColumn.setCellValueFactory(new PropertyValueFactory<>("parentPath"));
sizeColumn.setCellValueFactory(new PropertyValueFactory<>("size"));
domainColumn.prefWidthProperty().bind(this.filesTable.widthProperty().multiply(0.2));
nameColumn.prefWidthProperty().bind(this.filesTable.widthProperty().multiply(0.3));
pathColumn.prefWidthProperty().bind(this.filesTable.widthProperty().multiply(0.4));
sizeColumn.prefWidthProperty().bind(this.filesTable.widthProperty().multiply(0.1));

this.filesTable.getColumns().addAll(Arrays.asList(domainColumn, nameColumn, pathColumn, sizeColumn));
domainColumn.prefWidthProperty().bind(filesTable.widthProperty().multiply(0.2));
nameColumn.prefWidthProperty().bind(filesTable.widthProperty().multiply(0.3));
pathColumn.prefWidthProperty().bind(filesTable.widthProperty().multiply(0.4));
sizeColumn.prefWidthProperty().bind(filesTable.widthProperty().multiply(0.1));

this.filesTable.setRowFactory(tableView -> {
TableRow<BackupFileEntry> row = new TableRow<>();
filesTable.getColumns().addAll(Arrays.asList(domainColumn, nameColumn, pathColumn, sizeColumn));

filesTable.setRowFactory(tableView -> {
TableRow<BackupFileEntry> row = new TableRow<>();
row.itemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null || newValue.getFile().isEmpty()) return;
row.setContextMenu(FileActions.getContextMenu(
newValue.getFile().get(),
tableView.getScene().getWindow(),
removedIDs -> filesTable.getItems().removeIf(entry ->
entry.getFile().map(f -> removedIDs.contains(f.fileID)).orElse(false)
))
);
newValue.getFile().get(),
tableView.getScene().getWindow(),
removedIDs -> filesTable.getItems().removeIf(entry ->
entry.getFile().map(f -> removedIDs.contains(f.fileID)).orElse(false)
)
));
});

return row;
});
}

@FXML
public void searchFiles() {
try {
List<BackupFile> searchResult = selectedBackup.searchFiles(domainQueryField.getText(), relativePathQueryField.getText());

this.filesTable.setItems(FXCollections.observableList(searchResult.stream().map(BackupFileEntry::new).collect(Collectors.toList())));
List<BackupFile> searchResult = selectedBackup.searchFiles(
domainQueryField.getText(),
relativePathQueryField.getText()
);

filesTable.setItems(FXCollections.observableList(
searchResult.stream()
.map(BackupFileEntry::new)
.collect(Collectors.toList())
));
} catch (DatabaseConnectionException e) {
e.printStackTrace();
Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage());
Platform.runLater(() -> {
Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage());
});
}
}

@FXML
public void exportMatching() {
if (this.filesTable.getItems().size() == 0) return;
if (filesTable.getItems().isEmpty()) return;

DirectoryChooser chooser = new DirectoryChooser();
File destination = chooser.showDialog(this.filesTable.getScene().getWindow());

File destination = chooser.showDialog(filesTable.getScene().getWindow());
if (destination == null || !destination.exists()) return;

this.filesTable.getItems().forEach(backupFile -> {
if (backupFile.getFile().isEmpty()) return;
try {
backupFile.getFile().get().extractToFolder(destination, true);
} catch (IOException | BackupReadException | NotUnlockedException | UnsupportedCryptoException e) {
e.printStackTrace();
Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage(), ButtonType.OK);
// Create export task for progress bar
Task<Integer> exportTask = new Task<Integer>() {
@Override
protected Integer call() throws Exception {
List<BackupFileEntry> items = filesTable.getItems();
int successCount = 0;
int totalCount = items.size();

for (int i = 0; i < totalCount; i++) {
if (Thread.interrupted()) break; // for cancel

BackupFileEntry backupFile = items.get(i);
if (backupFile.getFile().isEmpty()) continue;

try {
backupFile.getFile().get().extractToFolder(destination, true);
successCount++;
} catch (IOException | BackupReadException
| NotUnlockedException | UnsupportedCryptoException e) {
e.printStackTrace();
}

// update progress
updateProgress(i + 1, totalCount);
updateMessage("Exporting " + (i + 1) + " of " + totalCount + " files...");
}

return successCount;
}
};

exportTask.setOnSucceeded(event -> {
Platform.runLater(() -> {
Integer successCount = exportTask.getValue();
int totalCount = filesTable.getItems().size();
String message = String.format(
"%d of %d files successfully exported to:%n%s",
successCount, totalCount, destination.getAbsolutePath()
);
Dialogs.showSuccessDialog(message);
});
});

exportTask.setOnFailed(event -> {
Throwable exception = exportTask.getException();
if (exception != null) {
exception.printStackTrace();
Platform.runLater(() -> {
Dialogs.showAlert(
Alert.AlertType.ERROR,
"Export error: " + exception.getMessage(),
ButtonType.OK
);
});
}
});

Dialogs.ProgressAlert progressAlert = new Dialogs.ProgressAlert("Exporting files...", exportTask, true);

// I *think* this is required because we have a progress bar now?
Thread exportThread = new Thread(exportTask);
exportThread.setDaemon(true);
exportThread.start();

progressAlert.showAndWait();
}

public void tabShown(ITunesBackup backup) {
if (backup == this.selectedBackup) return;

this.filesTable.setItems(null);
this.selectedBackup = backup;
if (backup == selectedBackup) return;
filesTable.setItems(null);
selectedBackup = backup;
}
}
}
Loading