diff --git a/src/main/java/me/maxih/itunes_backup_explorer/ui/Dialogs.java b/src/main/java/me/maxih/itunes_backup_explorer/ui/Dialogs.java index da9e081..d85da40 100644 --- a/src/main/java/me/maxih/itunes_backup_explorer/ui/Dialogs.java +++ b/src/main/java/me/maxih/itunes_backup_explorer/ui/Dialogs.java @@ -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; @@ -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 askPassword() { Dialog dialog = new Dialog<>(); dialog.setTitle("Enter the password"); @@ -26,7 +57,10 @@ public static Optional 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); @@ -34,41 +68,49 @@ public static Optional askPassword() { 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 showAlert(Alert.AlertType type, String message, ButtonType... buttonTypes) { - return getAlert(type, message, buttonTypes).showAndWait(); + public static Optional 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 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); } @@ -76,10 +118,18 @@ public ProgressAlert(String title, Task task, boolean cancellable) { 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(); + } +} \ No newline at end of file diff --git a/src/main/java/me/maxih/itunes_backup_explorer/ui/FileActions.java b/src/main/java/me/maxih/itunes_backup_explorer/ui/FileActions.java index bf57182..b9caa35 100644 --- a/src/main/java/me/maxih/itunes_backup_explorer/ui/FileActions.java +++ b/src/main/java/me/maxih/itunes_backup_explorer/ui/FileActions.java @@ -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); } diff --git a/src/main/java/me/maxih/itunes_backup_explorer/ui/FileSearchTabController.java b/src/main/java/me/maxih/itunes_backup_explorer/ui/FileSearchTabController.java index 3edb70b..39d8afa 100644 --- a/src/main/java/me/maxih/itunes_backup_explorer/ui/FileSearchTabController.java +++ b/src/main/java/me/maxih/itunes_backup_explorer/ui/FileSearchTabController.java @@ -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; @@ -30,31 +32,31 @@ public void initialize() { TableColumn nameColumn = new TableColumn<>("Name"); TableColumn pathColumn = new TableColumn<>("Path"); TableColumn 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 row = new TableRow<>(); + filesTable.getColumns().addAll(Arrays.asList(domainColumn, nameColumn, pathColumn, sizeColumn)); + filesTable.setRowFactory(tableView -> { + TableRow 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; }); } @@ -62,39 +64,102 @@ public void initialize() { @FXML public void searchFiles() { try { - List searchResult = selectedBackup.searchFiles(domainQueryField.getText(), relativePathQueryField.getText()); - - this.filesTable.setItems(FXCollections.observableList(searchResult.stream().map(BackupFileEntry::new).collect(Collectors.toList()))); + List 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 exportTask = new Task() { + @Override + protected Integer call() throws Exception { + List 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; } -} +} \ No newline at end of file diff --git a/src/main/java/me/maxih/itunes_backup_explorer/ui/FilesTabController.java b/src/main/java/me/maxih/itunes_backup_explorer/ui/FilesTabController.java index c899dc8..7f522f2 100644 --- a/src/main/java/me/maxih/itunes_backup_explorer/ui/FilesTabController.java +++ b/src/main/java/me/maxih/itunes_backup_explorer/ui/FilesTabController.java @@ -49,7 +49,6 @@ protected void updateItem(BackupFileEntry item, boolean empty) { } else { this.setPrefHeight(36); - CheckBox checkBox = new CheckBox(); checkBox.selectedProperty().bindBidirectional(item.checkBoxSelectedProperty()); checkBox.indeterminateProperty().bindBidirectional(item.checkBoxIndeterminateProperty()); @@ -69,7 +68,6 @@ protected void updateItem(BackupFileEntry item, boolean empty) { getDisclosureNode().setTranslateY(8); } - } }); @@ -91,13 +89,12 @@ protected TreeItem call() { } catch (DatabaseConnectionException | BackupReadException e) { e.printStackTrace(); } - return root; } }; - loadDomainFilesTask.valueProperty().addListener((obs, old, root) -> { - filesTreeView.setRoot(root); + loadDomainFilesTask.valueProperty().addListener((obs, oldRoot, newRoot) -> { + filesTreeView.setRoot(newRoot); domainsTreeView.setCursor(Cursor.DEFAULT); filesTreeView.setCursor(Cursor.DEFAULT); }); @@ -111,11 +108,11 @@ protected TreeItem call() { if (newValue == null || newValue.getFile().isEmpty()) return; TreeItem parent = getTreeItem().getParent(); setContextMenu(FileActions.getContextMenu( - newValue.getFile().get(), - splitPane.getScene().getWindow(), - // Children are automatically removed as well by the tree structure, so removedIDs can be ignored - removedIDs -> parent.getChildren().remove(getTreeItem())) - ); + newValue.getFile().get(), + splitPane.getScene().getWindow(), + // Children are automatically removed as well by the tree structure, so removedIDs can be ignored + removedIDs -> parent.getChildren().remove(getTreeItem()) + )); }); } @@ -127,7 +124,9 @@ protected void updateItem(BackupFileEntry item, boolean empty) { setText(null); setGraphic(null); } else { - boolean isDirectory = item.getFile().filter(f -> f.getFileType() == BackupFile.FileType.DIRECTORY).isPresent(); + boolean isDirectory = item.getFile() + .filter(f -> f.getFileType() == BackupFile.FileType.DIRECTORY) + .isPresent(); CheckBox checkBox = new CheckBox(); checkBox.selectedProperty().bindBidirectional(item.checkBoxSelectedProperty()); @@ -147,7 +146,6 @@ protected void updateItem(BackupFileEntry item, boolean empty) { setGraphic(graphic); setText(item.getDisplayName()); } - } }); } @@ -160,7 +158,7 @@ public void insertAsTree(TreeItem root, List i for (BackupFileEntry item : items) { int level = item.getPathLevel(); if (level > maxLevel) maxLevel = level; - if (!levels.containsKey(level)) levels.put(item.getPathLevel(), new ArrayList<>()); + if (!levels.containsKey(level)) levels.put(level, new ArrayList<>()); TreeItem treeItem = new TreeItem<>(item); item.selectionProperty().addListener((obs, prevSelection, selection) -> { @@ -191,27 +189,27 @@ public void insertAsTree(TreeItem root, List i parents.add(root); for (int currentLevel = 1; currentLevel <= maxLevel; currentLevel++) { List> children = levels.get(currentLevel); - if (children == null) - children = new ArrayList<>(); // will eventually fail but results in a nicer error message + if (children == null) children = new ArrayList<>(); // will eventually fail but results in a nicer error message for (TreeItem child : children) { BackupFileEntry childEntry = child.getValue(); Optional> parent = CollectionUtils.find(parents, parentCandidate -> { BackupFileEntry parentEntry = parentCandidate.getValue(); - if (parentEntry.getFile().isEmpty()) return false; BackupFile parentFile = parentEntry.getFile().get(); - return parentFile.getFileType() == BackupFile.FileType.DIRECTORY - && parentFile.domain.equals(childEntry.getDomain()) - && childEntry.getParentPath().equals(parentFile.relativePath); + && parentFile.domain.equals(childEntry.getDomain()) + && childEntry.getParentPath().equals(parentFile.relativePath); }); if (parent.isPresent()) { parent.get().getChildren().add(child); } else { - throw new BackupReadException("Missing parent directory: " + childEntry.getDomain() + "-" + BackupPathUtils.getParentPath(childEntry.getRelativePath())); + throw new BackupReadException( + "Missing parent directory: " + childEntry.getDomain() + "-" + + BackupPathUtils.getParentPath(childEntry.getRelativePath()) + ); } } @@ -232,7 +230,9 @@ public void tabShown(ITunesBackup backup) { domains = backup.queryDomainRoots(); } catch (DatabaseConnectionException e) { e.printStackTrace(); - Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage()); + Platform.runLater(() -> { + Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage()); + }); domains = Collections.emptyList(); } @@ -256,10 +256,11 @@ public void tabShown(ITunesBackup backup) { else root.getChildren().add(item); } - List> domainGroups = Arrays.asList(apps, appGroups, appPlugins, sysContainers, sysSharedContainers); + List> domainGroups = + Arrays.asList(apps, appGroups, appPlugins, sysContainers, sysSharedContainers); for (TreeItem domainGroup : domainGroups) { domainGroup.getValue().selectionProperty().addListener((observable, prevSelection, selection) -> - domainGroup.getChildren().forEach(group -> group.getValue().setSelection(selection)) + domainGroup.getChildren().forEach(group -> group.getValue().setSelection(selection)) ); } @@ -281,16 +282,42 @@ public void exportSelectedFiles() { if (destination == null) return; List selectedFiles = flattenAllChildren(filesTreeView.getRoot()) - .map(TreeItem::getValue) - .filter(entry -> entry.getSelection() != BackupFileEntry.Selection.NONE) - .map(BackupFileEntry::getFile) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); + .map(TreeItem::getValue) + .filter(entry -> entry.getSelection() != BackupFileEntry.Selection.NONE) + .map(BackupFileEntry::getFile) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + Task extractTask = exportFiles(selectedFiles, destination); + + extractTask.setOnSucceeded(event -> { + Platform.runLater(() -> { + Integer successCount = extractTask.getValue(); + int totalCount = selectedFiles.size(); + String message = String.format( + "%d of %d files successfully exported to:\n%s", + successCount, totalCount, destination.getAbsolutePath() + ); + Dialogs.showSuccessDialog(message); + }); + }); - Task extractTask = exportFiles(selectedFiles, destination); + extractTask.setOnFailed(event -> { + Throwable exception = extractTask.getException(); + if (exception != null) { + exception.printStackTrace(); + Platform.runLater(() -> { + Dialogs.showAlert( + Alert.AlertType.ERROR, + "Export error: " + exception.getMessage(), + ButtonType.OK + ); + }); + } + }); - Dialogs.ProgressAlert progress = new Dialogs.ProgressAlert("Extracting...", extractTask, true); + Dialogs.ProgressAlert progress = new Dialogs.ProgressAlert("Exporting selected files...", extractTask, true); new Thread(extractTask).start(); progress.showAndWait(); } @@ -302,42 +329,71 @@ public void exportSelectedDomains() { if (destination == null) return; String[] selectedDomains = flattenAllChildren(domainsTreeView.getRoot()) - .map(TreeItem::getValue) - .filter(entry -> entry.getSelection() != BackupFileEntry.Selection.NONE) - .map(BackupFileEntry::getFile) - .filter(Optional::isPresent) - .map(Optional::get) - .map(file -> file.domain) - .toArray(String[]::new); - - List selectedFiles = null; + .map(TreeItem::getValue) + .filter(entry -> entry.getSelection() != BackupFileEntry.Selection.NONE) + .map(BackupFileEntry::getFile) + .filter(Optional::isPresent) + .map(Optional::get) + .map(file -> file.domain) + .toArray(String[]::new); + + List selectedFiles; try { selectedFiles = selectedBackup.queryDomainFiles(true, selectedDomains); } catch (DatabaseConnectionException e) { e.printStackTrace(); Dialogs.showAlert(Alert.AlertType.ERROR, e.getMessage()); + return; } - Task extractTask = exportFiles(selectedFiles, destination); + Task extractTask = exportFiles(selectedFiles, destination); + + extractTask.setOnSucceeded(event -> { + Platform.runLater(() -> { + Integer successCount = extractTask.getValue(); + int totalCount = selectedFiles.size(); + String message = String.format( + "%d of %d files successfully exported to:\n%s", + successCount, totalCount, destination.getAbsolutePath() + ); + Dialogs.showSuccessDialog(message); + }); + }); + + extractTask.setOnFailed(event -> { + Throwable exception = extractTask.getException(); + if (exception != null) { + exception.printStackTrace(); + Platform.runLater(() -> { + Dialogs.showAlert( + Alert.AlertType.ERROR, + "Export error: " + exception.getMessage(), + ButtonType.OK + ); + }); + } + }); - Dialogs.ProgressAlert progress = new Dialogs.ProgressAlert("Extracting...", extractTask, true); + Dialogs.ProgressAlert progress = new Dialogs.ProgressAlert("Exporting selected domains...", extractTask, true); new Thread(extractTask).start(); progress.showAndWait(); } - private Task exportFiles(List files, File destination) { - return new Task<>() { + private Task exportFiles(List files, File destination) { + return new Task() { @Override - protected Void call() throws Exception { + protected Integer call() throws Exception { ButtonType skipButtonType = new ButtonType("Skip", ButtonBar.ButtonData.NEXT_FORWARD); ButtonType skipAllExistingButtonType = new ButtonType("Skip all existing", ButtonBar.ButtonData.NEXT_FORWARD); boolean skipExisting = false; + int successCount = 0; for (int i = 0; i < files.size(); i++) { try { if (Thread.interrupted()) break; files.get(i).extractToFolder(destination, true); - updateProgress(i, files.size()); + successCount++; + updateProgress(i + 1, files.size()); } catch (ClosedByInterruptException e) { break; } catch (FileAlreadyExistsException e) { @@ -346,18 +402,21 @@ protected Void call() throws Exception { if (file == null) file = ""; Optional response = showFileExportError( - "File already exists:\n" + file, skipButtonType, skipAllExistingButtonType, ButtonType.CANCEL); + "File already exists:\n" + file, + skipButtonType, skipAllExistingButtonType, ButtonType.CANCEL + ); if (response.isEmpty() || response.get() == ButtonType.CANCEL) break; if (response.get() == skipAllExistingButtonType) skipExisting = true; } catch (IOException | BackupReadException | NotUnlockedException | UnsupportedCryptoException e) { e.printStackTrace(); Optional response = showFileExportError( - e.getMessage() + "\nContinue?", ButtonType.YES, ButtonType.CANCEL); + e.getMessage() + "\nContinue?", ButtonType.YES, ButtonType.CANCEL + ); if (response.isEmpty() || response.get() == ButtonType.CANCEL) break; } } - return null; + return successCount; } }; } @@ -373,5 +432,4 @@ protected Optional call() { Platform.runLater(alertTask); return alertTask.get(); } - -} +} \ No newline at end of file diff --git a/src/main/java/me/maxih/itunes_backup_explorer/ui/WindowController.java b/src/main/java/me/maxih/itunes_backup_explorer/ui/WindowController.java index f419943..08772a5 100644 --- a/src/main/java/me/maxih/itunes_backup_explorer/ui/WindowController.java +++ b/src/main/java/me/maxih/itunes_backup_explorer/ui/WindowController.java @@ -13,9 +13,9 @@ import javafx.scene.control.*; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; -import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Stage; +import javafx.stage.DirectoryChooser; import me.maxih.itunes_backup_explorer.ITunesBackupExplorer; import me.maxih.itunes_backup_explorer.api.BackupReadException; import me.maxih.itunes_backup_explorer.api.ITunesBackup; @@ -31,6 +31,7 @@ import java.text.SimpleDateFormat; import java.util.List; import java.util.*; +import java.net.URI; public class WindowController { static final DateFormat BACKUP_DATE_FMT = new SimpleDateFormat("dd.MM.yyyy HH:mm"); @@ -213,13 +214,13 @@ public void openPreferences() { @FXML public void fileOpenBackup() { - FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("iTunes Backup", "Manifest.plist", "Manifest.db")); - File source = chooser.showOpenDialog(tabPane.getScene().getWindow()); - if (source == null) return; + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle("select iTunes Backup"); + File folder = chooser.showDialog(tabPane.getScene().getWindow()); + if (folder == null) return; try { - ITunesBackup backup = new ITunesBackup(source.getParentFile()); + ITunesBackup backup = new ITunesBackup(folder); this.loadBackup(backup); this.selectBackup(backup); } catch (FileNotFoundException e) { @@ -234,4 +235,16 @@ public void quit() { this.cleanUp(); Platform.exit(); } + + @FXML + public void openAbout() { + // didnt work on linux without this (program froze completely) + new Thread(() -> { + try { + Desktop.getDesktop().browse(new java.net.URI("https://github.com/MaxiHuHe04/iTunes-Backup-Explorer")); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } } diff --git a/src/main/resources/me/maxih/itunes_backup_explorer/window.fxml b/src/main/resources/me/maxih/itunes_backup_explorer/window.fxml index fbd6840..570a65c 100644 --- a/src/main/resources/me/maxih/itunes_backup_explorer/window.fxml +++ b/src/main/resources/me/maxih/itunes_backup_explorer/window.fxml @@ -24,7 +24,7 @@ - +