diff --git a/.gitignore b/.gitignore index a454f8e..fd5c7a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ .idea /client/target -#/server/client_folder/ -#/client/downloads/ +/server/clients_folders/ +/client/Cloud Storage downloads/ /server/target +/network/target /server/out -*.iml \ No newline at end of file +/server/log/ +*.iml +*.log \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..0c675e6 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..23f302a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM maven:3.6-alpine as DEPS + +WORKDIR /opt/app +COPY server.properties server.properties +COPY server/pom.xml server/pom.xml +COPY network/pom.xml network/pom.xml +COPY client/pom.xml client/pom.xml + +COPY pom.xml . + +FROM maven:3.6-alpine as BUILDER +WORKDIR /opt/app +COPY --from=deps /opt/app/ /opt/app +COPY server/src /opt/app/server/src +COPY network/src /opt/app/network/src +COPY client/src /opt/app/client/src + +RUN mvn -pl .,network,server clean package -Dhttps.protocols=TLSv1.2 + +FROM openjdk:8-jre-alpine +WORKDIR /opt/app +COPY --from=builder /opt/app/server/target/server-1.0-jar-with-dependencies.jar . +COPY --from=builder /opt/app/server/target/server.properties . +EXPOSE 8190 +CMD [ "java", "-jar", "server-1.0-jar-with-dependencies.jar" ] + diff --git a/README.md b/README.md index f96b3c0..57fe017 100644 --- a/README.md +++ b/README.md @@ -1 +1,43 @@ -CloudStorage +# Simple Cloud Storage App + +An example of a simple file manager with the ability to exchange files between a remote server and a client. Supports drag-and-drop actions, simple authorization and sing up functions. +The server implements the netty framework 4.1.51. + +* See client.properties and server.properties files to configure the app + + + +## Building + +#### Prerequisites: + + 1. Java (JDK) 8. + +#### Build + +```sh + You can use Maven to build the app: + From the project root, run ./mvnw clean install. +``` +**Open client/target and server/target folder and run "jar-with-dependencies.jar" files by using console.** + +**You should see "[main] INFO org.owpk.core.Server - Server started at : 8190" message from the server and "connected : localhost/127.0.0.1:8190" message from the client if everything is ok.** + +* By default, server creates a "clients_folders" directory in server root as default users files storage. Client creates a "Cloud Storage downloads" directory in client root as default download directory. +* If you want to customize some properties like port address, download dirs etc, look at client.properties and server.properties files, you can find it in project root or in target folder if you have already built the app. +* The server uses MySQL connection to authorize and add a new user, in project root you can find a DDL script to build your own database server, connection properties described in /server/src/resources/META-INF/persistence.xml file. +* You can also connect to the server with "test user" login and password. + +##Docker +* You can run the server in docker + - Install Docker on the machine you want to run it. + - Go to project root and run the commands below by using console +```sh + $docker build -t java-app:cloud . + $docker run java-app:cloud + + Or just pull it from repository + $docker pull vzvz4/cloud:cloud +``` + + diff --git a/client.properties b/client.properties index bf42cc3..45eacea 100644 --- a/client.properties +++ b/client.properties @@ -1,7 +1,7 @@ -#root_directory -#Fri Aug 07 22:50:37 YAKT 2020 -default_server=localhost -download_directory=./client/downloads/ +#last_directory +#Tue Aug 25 03:02:49 YAKT 2020 +last_directory=C\:\\ +download_directory=C\:\\Users\\vzvz4\\Desktop\\PAPKA\\cloudStorageProject\\engine\\CloudStorage\\.\\Cloud Storage downloads port=8190 +host=79.143.31.62 connect_on_startup=false -root_directory=C\:\\Users\\vzvz4\\Desktop\\PAPKA\\cloudStorageProject\\engine\\CloudStorage\\.\\client\\downloads diff --git a/client/downloads/File_to_upload.txt b/client/downloads/File_to_upload.txt deleted file mode 100644 index 8ec970b..0000000 --- a/client/downloads/File_to_upload.txt +++ /dev/null @@ -1 +0,0 @@ -upload this diff --git a/client/downloads/Test_file.txt b/client/downloads/Test_file.txt deleted file mode 100644 index 051463c..0000000 --- a/client/downloads/Test_file.txt +++ /dev/null @@ -1,3 +0,0 @@ -Hello world! -Hello world! -Hello world! diff --git a/client/pom.xml b/client/pom.xml index c9e6176..5953596 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -10,14 +10,71 @@ 4.0.0 client - - - org.owpk - network - 1.0 - compile - - + jar + + 1.11 + + + + + maven-assembly-plugin + ${maven.assembly.version} + + + package + + single + + + + + + + true + org.owpk.app.Client + + + + jar-with-dependencies + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + test + + run + + + + Using env.test.properties + + + + + + + + + + + + commons-codec + commons-codec + ${commos.codec.version} + + + + org.owpk + network + 1.0 + compile + + \ No newline at end of file diff --git a/client/src/main/java/org/owpk/IODataHandler/AbsHandler.java b/client/src/main/java/org/owpk/IODataHandler/AbsHandler.java new file mode 100644 index 0000000..fca545a --- /dev/null +++ b/client/src/main/java/org/owpk/IODataHandler/AbsHandler.java @@ -0,0 +1,37 @@ +package org.owpk.IODataHandler; + +import io.netty.handler.codec.serialization.ObjectDecoderInputStream; +import io.netty.handler.codec.serialization.ObjectEncoderOutputStream; +import org.owpk.message.Message; +import org.owpk.network.IONetworkServiceImpl; + +import java.io.IOException; + +public abstract class AbsHandler { + private boolean handlerOver; + + protected void initDataListener() throws IOException, ClassNotFoundException { + System.out.println("DataListener started : " + this.getClass().toString() + " : handler: " + handlerOver); + ObjectDecoderInputStream in = (ObjectDecoderInputStream) IONetworkServiceImpl.getService().getIn(); + Message msg; + while (!handlerOver) { + if (in.available() > 0) { + msg = (Message) in.readObject(); + listen(msg); + } + } + } + + protected void writeMessage(Message message) throws IOException { + ((ObjectEncoderOutputStream) IONetworkServiceImpl + .getService() + .getOut()) + .writeObject(message); + } + + public void setHandlerOver(boolean handlerOver) { + this.handlerOver = handlerOver; + } + protected abstract void listen(Message message) throws IOException; + public abstract void execute() throws InterruptedException, IOException, ClassNotFoundException; +} diff --git a/client/src/main/java/org/owpk/IODataHandler/AuthHandler.java b/client/src/main/java/org/owpk/IODataHandler/AuthHandler.java new file mode 100644 index 0000000..1b75b70 --- /dev/null +++ b/client/src/main/java/org/owpk/IODataHandler/AuthHandler.java @@ -0,0 +1,115 @@ +package org.owpk.IODataHandler; + +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.owpk.controller.UserDialog; +import org.owpk.message.Message; +import org.owpk.message.MessageType; +import org.owpk.message.UserInfo; +import org.owpk.network.IONetworkServiceImpl; + +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; + +/** + * Отпарвляет форму с данными на сервер для аутентификации, слушает ответ + * @see #initDataListener() + * @see #listen(Message) + */ +@Getter +@Setter +public class AuthHandler extends AbsHandler { + private final Logger log = LogManager.getLogger(AuthHandler.class.getName()); + private final CountDownLatch doneLatch = new CountDownLatch(1); + + private String hash(String input) { + return DigestUtils.sha256Hex(input); + } + + private void showDialog() { + Platform.runLater(() -> { + loginDialog(); + doneLatch.countDown(); + }); + } + + @Override + protected void listen(Message msg) { + if (msg.getType() == MessageType.OK) { + setHandlerOver(true); + IONetworkServiceImpl.getService().addMainDataHandler(); + } else if (msg.getType() == MessageType.ERROR) { + UserDialog.errorDialog((String) msg.getPayload()); + IONetworkServiceImpl.getService().addHandlerToPipeline(new AuthHandler()); + setHandlerOver(true); + } + } + + //sync + @Override + public void execute() throws InterruptedException { + showDialog(); + doneLatch.await(); + } + + private void loginDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle("Login"); + dialog.setHeaderText("Authentication\nTest login: user\nTest password: 1234"); + + ButtonType loginButtonType = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); + ButtonType signUpButtonType = new ButtonType("Sign up"); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + + dialog.getDialogPane().getButtonTypes().setAll(signUpButtonType, loginButtonType, buttonTypeCancel); + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + TextField username = new TextField(); + username.setPromptText("Username"); + PasswordField password = new PasswordField(); + password.setPromptText("Password"); + + grid.add(new Label("Username:"), 0, 0); + grid.add(username, 1, 0); + grid.add(new Label("Password:"), 0, 1); + grid.add(password, 1, 1); + + Node loginButton = dialog.getDialogPane().lookupButton(loginButtonType); + loginButton.setDisable(true); + + username.textProperty().addListener((observable, oldValue, newValue) -> loginButton.setDisable(newValue.trim().isEmpty())); + + dialog.getDialogPane().setContent(grid); + + Optional result = dialog.showAndWait(); + if (result.get() == loginButtonType) { + try { + writeMessage(new UserInfo(MessageType.AUTH, username.getText(), hash(password.getText()))); + initDataListener(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + else if (result.get() == signUpButtonType) { + System.out.println("SIGN OPTION"); + IONetworkServiceImpl.getService().addHandlerToPipeline(new SignHandler()); + setHandlerOver(true); + } else { + IONetworkServiceImpl.getService().disconnect(); + } + } + +} diff --git a/client/src/main/java/org/owpk/IODataHandler/InputDataHandler.java b/client/src/main/java/org/owpk/IODataHandler/InputDataHandler.java new file mode 100644 index 0000000..17dadd3 --- /dev/null +++ b/client/src/main/java/org/owpk/IODataHandler/InputDataHandler.java @@ -0,0 +1,92 @@ +package org.owpk.IODataHandler; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.owpk.message.MessageType; +import org.owpk.util.Callback; +import org.owpk.app.ClientConfig; +import org.owpk.message.DataInfo; +import org.owpk.message.Message; +import org.owpk.network.IONetworkServiceImpl; +import org.owpk.network.NetworkServiceInt; +import org.owpk.util.FileInfo; +import org.owpk.util.FileUtility; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; + +/** + * Основной класс обработчик входных данных + */ +public class InputDataHandler extends AbsHandler { + private final Logger log = LogManager.getLogger(InputDataHandler.class.getName()); + private final Callback serverStatusLabel; + private final Callback> tableViewCallback; + private final Callback progressBarCallback; + private final Callback refreshClientCallback; + private final NetworkServiceInt networkServiceInt; + + public InputDataHandler(Callback... callbacks) throws IOException { + this.tableViewCallback = callbacks[0]; + this.serverStatusLabel = callbacks[1]; + this.progressBarCallback = callbacks[2]; + this.refreshClientCallback = callbacks[3]; + this.networkServiceInt = IONetworkServiceImpl.getService(); + } + + @Override + protected void listen(Message msg) throws IOException { + log.info(msg); + switch (msg.getType()) { + case DIR: + List dirList = (List) msg.getPayload(); + tableViewCallback.call(dirList); + break; + case DOWNLOAD: + download((DataInfo) msg); + break; + case OK: + case ERROR: + log.error(msg.getPayload()); + break; + case DEFAULT: + } + } + + @Override + public void execute() { + new Thread(() -> { + try { + log.info("thread started"); + writeMessage(new Message<>(MessageType.DIR)); + initDataListener(); + } catch (IOException | ClassNotFoundException e) { + log.error(e); + serverStatusLabel.call("network error: " + ClientConfig.getDefaultServer()); + e.printStackTrace(); + } finally { + networkServiceInt.disconnect(); + } + }).start(); + } + + /** + * @see FileUtility.FileWriter + */ + private void download(DataInfo ms) throws IOException { + String fileName = ms.getFile(); + final File f = new File( + ClientConfig.getConfig().getDownloadDirectory().toString() + "\\" + fileName); + FileUtility.FileWriter writer = FileUtility.FileWriter.getWriter(f.getAbsolutePath()); + double count = (float) ms.getChunkIndex() / ms.getChunkCount(); + progressBarCallback.call(count); + writer.assembleChunkedFile(ms); + if (ms.getChunkIndex() == ms.getChunkCount() - 1){ + serverStatusLabel.call("done"); + progressBarCallback.call(0D); + refreshClientCallback.call(ClientConfig.getConfig().getDownloadDirectory().toAbsolutePath()); + } + } +} diff --git a/client/src/main/java/org/owpk/IODataHandler/SignHandler.java b/client/src/main/java/org/owpk/IODataHandler/SignHandler.java new file mode 100644 index 0000000..6d29435 --- /dev/null +++ b/client/src/main/java/org/owpk/IODataHandler/SignHandler.java @@ -0,0 +1,121 @@ +package org.owpk.IODataHandler; + +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import lombok.Getter; +import lombok.Setter; +import org.owpk.controller.UserDialog; +import org.owpk.message.Message; +import org.owpk.message.MessageType; +import org.owpk.message.UserInfo; +import org.owpk.network.IONetworkServiceImpl; + +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; + +/** + * Отправляет форму с данными юзера на сервер для регистрации, слушает ответ + * @see #initDataListener() + * @see #listen(Message) + */ +@Getter +@Setter +public class SignHandler extends AbsHandler{ + private final CountDownLatch doneLatch = new CountDownLatch(1); + + private void showDialog() { + Platform.runLater(this::signDialog); + } + + @Override + protected void listen(Message message) throws IOException { + switch (message.getType()) { + case OK: + Platform.runLater(() -> { + UserDialog.confirmDialog(message.getPayload().toString(), null); + IONetworkServiceImpl.getService().addHandlerToPipeline(new AuthHandler()); + setHandlerOver(true); + doneLatch.countDown(); + }); + break; + case ERROR: + Platform.runLater(() -> { + UserDialog.errorDialog(message.getPayload().toString()); + showDialog(); + doneLatch.countDown(); + }); + break; + } + } + + //sync + @Override + public void execute() throws InterruptedException { + showDialog(); + doneLatch.await(); + } + + public void signDialog() { + Dialog dialog = new Dialog<>(); + dialog.setTitle("Sign"); + dialog.setHeaderText("Please enter login and password"); + + ButtonType signButtonType = new ButtonType("Sign up", ButtonBar.ButtonData.OK_DONE); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + + dialog.getDialogPane().getButtonTypes().setAll(signButtonType, buttonTypeCancel); + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + TextField username = new TextField(); + username.setPromptText("Username"); + TextField email = new TextField(); + email.setPromptText("Email"); + PasswordField password = new PasswordField(); + password.setPromptText("Password"); + PasswordField confirmPassword = new PasswordField(); + confirmPassword.setPromptText("Confirm password"); + grid.add(new Label("Username:"), 0, 0); + grid.add(username, 1, 0); + grid.add(new Label("Email:"), 0, 1); + grid.add(email, 1, 1); + grid.add(new Label("Password:"), 0, 3); + grid.add(password, 1, 3); + grid.add(new Label("Confirm password:"), 0, 4); + grid.add(confirmPassword, 1, 4); + + Node signButton = dialog.getDialogPane().lookupButton(signButtonType); + signButton.setDisable(true); + username.textProperty().addListener((observable, oldValue, newValue) -> signButton.setDisable(newValue.trim().isEmpty())); + dialog.getDialogPane().setContent(grid); + + Platform.runLater(username::requestFocus); + Optional result = dialog.showAndWait(); + if (result.get() == signButtonType) { + try { + writeMessage(new UserInfo(MessageType.SIGN, username.getText(), password.getText(), email.getText())); + new Thread(() -> { + try { + setHandlerOver(false); + initDataListener(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + }).start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + else { + IONetworkServiceImpl.getService().disconnect(); + doneLatch.countDown(); + } + } +} diff --git a/client/src/main/java/org/owpk/app/Client.java b/client/src/main/java/org/owpk/app/Client.java index 18d4f2a..ca0afdc 100644 --- a/client/src/main/java/org/owpk/app/Client.java +++ b/client/src/main/java/org/owpk/app/Client.java @@ -33,5 +33,4 @@ public void start(Stage primaryStage) throws Exception { primaryStage.show(); } - } diff --git a/client/src/main/java/org/owpk/app/ClientConfig.java b/client/src/main/java/org/owpk/app/ClientConfig.java index dfe528c..8b2275b 100644 --- a/client/src/main/java/org/owpk/app/ClientConfig.java +++ b/client/src/main/java/org/owpk/app/ClientConfig.java @@ -1,21 +1,20 @@ package org.owpk.app; +import javafx.application.Platform; +import org.owpk.controller.UserDialog; import org.owpk.util.Config; -import javax.swing.filechooser.FileSystemView; -import java.io.*; -import java.nio.file.Files; +import java.io.File; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Properties; /** - * читает и перезаписывает файл {@link #CONFIG_NAME}, - * устанавливает последнюю посещенную диркеторию, - * создает синглтон {@link ClientConfig} + * Class to read and overwrite the file {@link #CONFIG_NAME}. + * Sets the last visited directory. + * Creates singleton {@link ClientConfig}. */ public class ClientConfig extends Config { private static final String CONFIG_NAME = "client.properties"; + private static final String DEFAULT_DOWNLOAD_DIR = "./Cloud Storage downloads/"; private static final String DEFAULT_SERVER = "localhost"; private Path downloadDirectory; private Path startPath; @@ -23,41 +22,41 @@ public class ClientConfig extends Config { private static ClientConfig config; public static ClientConfig getConfig() { - return config == null? new ClientConfig() : config; + if (config == null) + config = new ClientConfig(); + return config; } private ClientConfig() { super(CONFIG_NAME); } + /** + * load parameters in a config file client.properties, + * sets the starting path, if it doesn't exists sets first parameter from filesystem roots list + */ @Override public void load() { - downloadDirectory = Paths.get(properties.getProperty(ConfigParameters.DOWNLOAD_DIR.getDescription(), null)); - startPath = Paths.get(properties.getProperty(ConfigParameters.LAST_DIR.getDescription(), null)); + String propDwnld = properties.getProperty(ConfigParameters.DOWNLOAD_DIR.getDescription(), DEFAULT_DOWNLOAD_DIR); + File f = new File(propDwnld); + if (!f.exists()) { + Platform.runLater(() -> { + File temp = new File(DEFAULT_DOWNLOAD_DIR); + temp.mkdirs(); + downloadDirectory = temp.toPath(); + writeProperty(ConfigParameters.DOWNLOAD_DIR, downloadDirectory.toAbsolutePath().toString()); + UserDialog.infoDialog("Could not find download directory","Default download folder created:\n" + temp.getAbsolutePath()); + }); + } else downloadDirectory = f.toPath(); + String strPath = properties.getProperty(ConfigParameters.LAST_DIR.getDescription()); + File temp = new File(strPath); + if (strPath.isEmpty() || !temp.exists()) + startPath = File.listRoots()[0].toPath(); + else startPath = temp.toPath(); port = checkPort(properties.getProperty(ConfigParameters.PORT.getDescription(), null)); host = properties.getProperty(ConfigParameters.HOST.getDescription(), DEFAULT_SERVER); } - public void setDownloadDirectory(String path) { - downloadDirectory = Paths.get(path); - writeProperty(ConfigParameters.DOWNLOAD_DIR, path); - } - - public void setStartPath(String path) { - startPath = Paths.get(path); - writeProperty(ConfigParameters.LAST_DIR, path); - } - - public void writeProperty(ConfigParameters prop, String val) { - try(FileWriter fw = new FileWriter(new File(CONFIG_NAME))) { - properties.setProperty(prop.getDescription(), val); - properties.store(fw, prop.getDescription()); - System.out.println(properties.getProperty(prop.getDescription()) + " -- " + prop.getDescription() + " : new property"); - } catch (IOException e) { - e.printStackTrace(); - } - } - public static String getDefaultServer() { return DEFAULT_SERVER; } @@ -73,4 +72,8 @@ public Path getStartPath() { public String getHost() { return host; } + + public static String getDefaultDownloadDir() { + return DEFAULT_DOWNLOAD_DIR; + } } diff --git a/client/src/main/java/org/owpk/controller/ClientPanelController.java b/client/src/main/java/org/owpk/controller/ClientPanelController.java index fe23b02..d30c28c 100644 --- a/client/src/main/java/org/owpk/controller/ClientPanelController.java +++ b/client/src/main/java/org/owpk/controller/ClientPanelController.java @@ -11,8 +11,9 @@ import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import lombok.SneakyThrows; -import org.owpk.app.Callback; -import org.owpk.app.ClientConfig; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.owpk.util.Callback; import org.owpk.util.FileInfo; import org.owpk.util.FileUtility; @@ -27,11 +28,12 @@ import java.util.stream.Collectors; public class ClientPanelController { + private final Logger log = LogManager.getLogger(ClientPanelController.class.getName()); @FXML private TableView client_panel; @FXML private Button client_forward_btn; + @FXML private Button client_back; @FXML private TextField client_textFlow; @FXML private ComboBox disk_list; - @FXML private Button client_back; @FXML private VBox client_panel_vbox; private MainSceneController mainSceneController; @@ -46,7 +48,7 @@ public class ClientPanelController { private Desktop desktop; /** - * кнопка вперед по истории + * go forward button */ @FXML public void onForwardInClientHistory() { @@ -55,11 +57,10 @@ public void onForwardInClientHistory() { clientBackInHistoryStack.push(clientForwardInHistoryStack.pop()); clientRefresh(p); } - showBhistory(); } /** - * кнопка назад по истории + * go back button */ @FXML public void onBackInClientHistory(ActionEvent actionEvent) { @@ -68,19 +69,17 @@ public void onBackInClientHistory(ActionEvent actionEvent) { Path p = clientBackInHistoryStack.peek(); clientRefresh(p); } - showFhistory(); } /** - * кнопка вверх по директории + * directory up button */ @FXML public void onUpBtnClicked(ActionEvent actionEvent) { - Path p = clientBackInHistoryStack.peek().getParent(); + final Path p = clientBackInHistoryStack.peek().getParent(); if (p != null) { clientRefresh(p); clientBackInHistoryStack.push(p); - showBhistory(); } } @@ -93,7 +92,8 @@ private void resetStatusLabel() { } /** - * обновляет таблицу локальных файлов + * Update local table + * @see FileInfo */ public void clientRefresh(Path p) { try { @@ -107,6 +107,7 @@ public void clientRefresh(Path p) { .collect(Collectors.toList())); client_panel.sort(); } catch (IOException e) { + log.error(e); mainSceneController.setStatusLabel("can't open"); e.printStackTrace(); } @@ -116,13 +117,6 @@ public void clientRefresh() { clientRefresh(clientBackInHistoryStack.peek()); } - private void showBhistory() { - System.out.println(clientBackInHistoryStack + " <--- back"); - } - private void showFhistory() { - System.out.println(clientForwardInHistoryStack + " <--- forward"); - } - private void initCallbacks() { textFlowCallback = s -> { Platform.runLater(()-> { client_textFlow.setText(s); @@ -149,11 +143,10 @@ private void initListeners() { if (x.getClickCount() == 2 && x.getButton() == MouseButton.PRIMARY) { FileInfo f = client_panel.getSelectionModel().getSelectedItem(); if (f.getFileType() == FileInfo.FileType.DIRECTORY) { - Path p = f.getPath(); + final Path p = f.getPath(); clientRefresh(p); client_textFlow.setText(p.toString()); clientBackInHistoryStack.push(p); - showBhistory(); } else { File file = f.getPath().toFile(); if (file.exists()) { @@ -165,21 +158,20 @@ private void initListeners() { } /** - * инициализирует DragAndDrop слушателей + * Initializing drag-and-drop listeners. + * Detect drag over, drag entered and drag dropped events. */ - private Path targetDirectory; private void initDragAndDropListeners() { final FileInfo[] tempItem = new FileInfo[1]; client_panel.setOnDragDetected(x -> { - FileInfo f = client_panel.getSelectionModel().getSelectedItem(); + final FileInfo f = client_panel.getSelectionModel().getSelectedItem(); File from; if (f != null) { from = f.getPath().toFile(); - System.out.println(f.getPath()); - Dragboard db = client_panel.startDragAndDrop(TransferMode.MOVE); - ClipboardContent content = new ClipboardContent(); + final Dragboard db = client_panel.startDragAndDrop(TransferMode.MOVE); + final ClipboardContent content = new ClipboardContent(); content.putString(from.getAbsolutePath()); db.setContent(content); x.consume(); @@ -192,10 +184,8 @@ private void initDragAndDropListeners() { //здесь отслеживается target путь, это можно сделать только через RowFactory client_panel.setRowFactory(x -> { - TableRow row = new TableRow<>(); - row.setOnDragDropped(event -> { - tempItem[0] = row.getItem(); - }); + final TableRow row = new TableRow<>(); + row.setOnDragDropped(event -> tempItem[0] = row.getItem()); return row; }); @@ -205,31 +195,26 @@ private void initDragAndDropListeners() { targetDirectory = clientBackInHistoryStack.peek(); }); - //если событие завершено берем source путь из TreeItem, используем Files.move(), + //если событие завершено, берем source путь из TreeItem, используем Files.move(), // в этом случае обертку FileUtils, в которой дополнительно отслеживается - - // перемещается папка или файл и возможные Exception, + // перемещается папка или файл и возможные исключения, // после того как все успешно переместилось обновляем оба TableView через MainController // иначе обновляется только одна таблица на которой фокус client_panel.setOnDragDropped(x -> { x.acceptTransferModes(TransferMode.ANY); - Dragboard db = x.getDragboard(); + final Dragboard db = x.getDragboard(); boolean success = false; if (db.hasString()) { - Path source = Paths.get(db.getString()); - Path target = tempItem[0] != null ? tempItem[0].getPath() : targetDirectory; + final Path source = Paths.get(db.getString()); + final Path target = tempItem[0] != null ? tempItem[0].getPath() : targetDirectory; if (!source.equals(target)) { - System.out.println("-Move from: " + source); - System.out.println("-Move to: " + target); try { FileUtility.move(source, target); mainSceneController.refreshAllClientPanels(); mainSceneController.setStatusLabel("done"); success = true; } catch (IOException e) { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle("Can't move file"); - alert.setContentText(e.toString()); - alert.showAndWait(); + UserDialog.errorDialog("can't move file \n" + e.getLocalizedMessage()); e.printStackTrace(); } } @@ -244,7 +229,7 @@ private void openFile(File file) { try { desktop.open(file); } catch (IOException ex) { - System.out.println(ex.getLocalizedMessage()); + log.error(ex); } } @@ -276,4 +261,5 @@ public void init() { initListeners(); client_panel.setPlaceholder(new Label("")); } + } diff --git a/client/src/main/java/org/owpk/controller/CloudPanelController.java b/client/src/main/java/org/owpk/controller/CloudPanelController.java index 71e7a55..5957480 100644 --- a/client/src/main/java/org/owpk/controller/CloudPanelController.java +++ b/client/src/main/java/org/owpk/controller/CloudPanelController.java @@ -5,22 +5,23 @@ import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; -import org.owpk.app.Callback; +import javafx.scene.layout.HBox; import org.owpk.app.ClientConfig; -import org.owpk.message.DataInfo; -import org.owpk.message.MessageType; import org.owpk.message.Message; -import org.owpk.network.InputDataHandler; +import org.owpk.message.MessageType; import org.owpk.network.NetworkServiceFactory; import org.owpk.network.NetworkServiceInt; +import org.owpk.util.Callback; import org.owpk.util.FileInfo; import org.owpk.util.FileUtility; +import org.owpk.util.OutputCallback; import java.io.File; import java.io.IOException; @@ -45,11 +46,12 @@ public class CloudPanelController { private Callback> cloudTableCallback; private Callback statusLabelCallback; private Callback progressBarCallback; + + /** - * метод вызывается при нажатии на кнопку "connect" - * {@link NetworkServiceFactory} возвращает {@link NetworkServiceInt}, - * который создает подключение клиента к серверу, - * данные для созданного подключения обслуживает {@link InputDataHandler} + * invoking when "connect" button has been clicked + * {@link NetworkServiceFactory} returns {@link NetworkServiceInt}, + * which creates a client-to-server connection */ public void connect() { Service ser = new Service() { @@ -57,24 +59,22 @@ public void connect() { protected Task createTask() { return new Task() { @Override - protected Void call() throws InterruptedException, IOException { + protected Void call() throws InterruptedException { try { - networkServiceInt = NetworkServiceFactory.getHandler(ClientConfig.getDefaultServer()); + if (networkServiceInt != null) networkServiceInt.disconnect(); + networkServiceInt = NetworkServiceFactory.getService(ClientConfig.getDefaultServer()); + networkServiceInt.initHandlers(cloudTableCallback, + statusLabelCallback, + progressBarCallback, + mainSceneController.getClientPanelController().getRefreshPanelCallback()); networkServiceInt.connect(); - InputDataHandler inputDataHandler = - new InputDataHandler( - networkServiceInt, - cloudTableCallback, - statusLabelCallback, - progressBarCallback, - mainSceneController.getClientPanelController().getRefreshPanelCallback() - ); - networkServiceInt.initDataHandler(inputDataHandler); - updateServerFolders(); - } catch (IOException e) { + mainSceneController.getClientPanelController().getHistory().push(ClientConfig.getConfig().getDownloadDirectory()); + mainSceneController.getClientPanelController().clientRefresh(ClientConfig.getConfig().getDownloadDirectory()); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); disconnect(); networkServiceInt = null; - throw new InterruptedException(); + throw new InterruptedException(e.getMessage()); } return null; } @@ -85,11 +85,13 @@ protected Void call() throws InterruptedException, IOException { ser.setOnRunning((WorkerStateEvent event) -> mainSceneController.setStatusLabel("trying to connect " + ClientConfig.getDefaultServer() + "...")); ser.setOnSucceeded((WorkerStateEvent event) -> - mainSceneController.setStatusLabel("connected: " + networkServiceInt.getName())); + mainSceneController.setStatusLabel("")); ser.setOnFailed((WorkerStateEvent event) -> { - mainSceneController.setStatusLabel("unable to connect"); - UserDialog.errorDialog("Can't connect to server"); - }); + mainSceneController.setStatusLabel("connection error"); + UserDialog.errorDialog(event.getSource().getException().getMessage()); + UserDialog.showConnectionParameters(); + event.getSource().getException().printStackTrace(); + }); ser.start(); } @@ -100,14 +102,9 @@ protected Task createTask() { return new Task() { @Override protected Void call() throws InterruptedException, IOException { - DataInfo[] bufferedData = FileUtility.getChunkedFile(f, MessageType.UPLOAD); - float counter; - for (int i = 0; i < bufferedData.length; i++) { - sendMessage(bufferedData[i]); - counter = (float) i / bufferedData.length; - float finalCounter = counter; - Platform.runLater(() -> progress_cloud.setProgress(finalCounter)); - } + Callback progressBarCallback = x -> Platform.runLater(() -> progress_cloud.setProgress(x)); + OutputCallback> out = CloudPanelController.this::sendMessage; + FileUtility.sendFileByChunks(out, f, MessageType.UPLOAD, progressBarCallback); return null; } }; @@ -127,11 +124,10 @@ protected Void call() throws InterruptedException, IOException { } /** - * Отправляет серверу команду {@link MessageType} - * сервер возвращает List - * @throws IOException + * Sends the {@link MessageType} command to the server + * server should return List */ - public void updateServerFolders() throws IOException { + public void updateServerFolders() { if (networkServiceInt == null) { connect(); } else @@ -154,7 +150,6 @@ private void serverRefresh(List list) { private void initListeners() { final FileInfo[] tempItem = new FileInfo[1]; - server_panel.setRowFactory(x -> { TableRow row = new TableRow<>(); row.setOnDragDropped(event -> { @@ -186,62 +181,79 @@ public void setMainSceneController(MainSceneController mainSceneController) { void disconnect() { if (networkServiceInt != null) { - try { - networkServiceInt.disconnect(); - } catch (IOException e) { - e.printStackTrace(); - } + networkServiceInt.disconnect(); + networkServiceInt = null; } } /** - * Добавляет колонку с кнопкой "скачать" в cloud таблицу - * Посылает на сервер команду {@link MessageType} DOWNLOAD и имя файла + * Adds a column with a "download" button to the cloud table + * Sends the {@link MessageType} DOWNLOAD command and file name to the server */ private void initDownloadAction() { TableColumn server_column_action = new TableColumn<>("Action"); server_column_action.setCellValueFactory(new PropertyValueFactory<>("fileType")); - javafx.util.Callback, TableCell> cellFactory - = param -> { - final TableCell cell = new TableCell() { - final Button btn = new Button(); - { - btn.setMaxWidth(30); - btn.setPadding(Insets.EMPTY); - btn.setGraphic(new IconBuilder() - .setIconImage("download") - .setFitHeight(15) - .build()); - } - @Override - protected void updateItem(FileInfo.FileType item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == FileInfo.FileType.DIRECTORY) { - setGraphic(null); - } else { - btn.setOnAction(event -> { - FileInfo info = getTableView().getItems().get(getIndex()); - sendMessage(new Message<>(MessageType.DOWNLOAD, info.getFilename())); - }); - setGraphic(btn); - } - setText(null); - } - }; - return cell; + = param -> new TableCell() { + final Button downloadBtn = new Button(); + final Button deleteBtn = new Button("✕"); + final HBox hBox = new HBox(); + + { + hBox.setPadding(Insets.EMPTY); + downloadBtn.setMaxWidth(35); + downloadBtn.setPadding(Insets.EMPTY); + downloadBtn.setGraphic(new IconBuilder() + .setIconImage("download") + .setFitHeight(15) + .build()); + deleteBtn.setMaxWidth(35); + deleteBtn.setPadding(Insets.EMPTY); + downloadBtn.setOnAction(event -> { + FileInfo info = getTableView().getItems().get(getIndex()); + sendMessage(new Message<>(MessageType.DOWNLOAD, info.getFilename())); + }); + deleteBtn.setOnAction(event -> { + FileInfo info = getTableView().getItems().get(getIndex()); + sendMessage(new Message<>(MessageType.DELETE, info.getFilename())); + }); + hBox.getChildren().add(downloadBtn); + hBox.getChildren().add(deleteBtn); + } + + @Override + protected void updateItem(FileInfo.FileType item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == FileInfo.FileType.DIRECTORY) { + setGraphic(null); + } else + setGraphic(hBox); + setText(null); + } }; server_column_action.setCellFactory(cellFactory); server_panel.getColumns().add(server_column_action); } + public void onUpBtnClicked(ActionEvent actionEvent) { + } + + private void initCallbacks() { + statusLabelCallback = s -> Platform.runLater(() -> mainSceneController.setStatusLabel(s)); + progressBarCallback = i -> Platform.runLater(() -> progress_cloud.setProgress(i)); + } + + public NetworkServiceInt getNetworkServiceInt() { + return networkServiceInt; + } + public void init() { initListeners(); connect(); cloudTableCallback = this::serverRefresh; initDownloadAction(); - statusLabelCallback = s -> mainSceneController.setStatusLabel(s); - progressBarCallback = i -> Platform.runLater(() -> progress_cloud.setProgress(i)); + initCallbacks(); } + } diff --git a/client/src/main/java/org/owpk/controller/MainSceneController.java b/client/src/main/java/org/owpk/controller/MainSceneController.java index 71b1a98..b98fbd1 100644 --- a/client/src/main/java/org/owpk/controller/MainSceneController.java +++ b/client/src/main/java/org/owpk/controller/MainSceneController.java @@ -10,6 +10,7 @@ import javafx.stage.Stage; import lombok.SneakyThrows; import org.owpk.app.ClientConfig; +import org.owpk.util.Config; import java.io.File; import java.io.IOException; @@ -19,7 +20,8 @@ import java.util.ResourceBundle; /** - * основной контроллер + * Main controller class + * Contains client panel, cloud panel and directory tree */ public class MainSceneController implements Initializable { @FXML private MenuBar drag_menu; @@ -54,8 +56,8 @@ public Stage getStage() { } /** - * инициализация ресайзера, кнопок управления окном - * и драг опции для верхнего MenuBar элемента + * Initialization of the resize helper, window control buttons + * and drag options for the top MenuBar element */ public void initWindowControls(Stage stage) { this.stage = stage; @@ -63,7 +65,9 @@ public void initWindowControls(Stage stage) { //кнопка закрыть shut_down_btn.setOnMouseClicked(event -> { - config.setStartPath(clientPanelController.getHistory().peek().toString()); + if (cloudPanelController.getNetworkServiceInt() != null) + cloudPanelController.disconnect(); + config.writeProperty(Config.ConfigParameters.LAST_DIR, clientPanelController.getHistory().peek().toString()); Platform.exit(); }); @@ -123,18 +127,6 @@ private void fillElements() { Platform.runLater(() -> TreeViewController.setupTreeView(tree_view)); } - public void checkConfig() { - System.out.println("Checking config"); - File p = new File(config.getDownloadDirectory().toString()); - if (!p.exists()) { - p.mkdirs(); - UserDialog.confirmDialog(p.getAbsolutePath()); - } - Path path = Paths.get(p.getAbsolutePath()); - clientPanelController.getHistory().push(path); - clientPanelController.clientRefresh(path); - } - public ClientConfig getConfig() { return config; } @@ -154,9 +146,8 @@ public CloudPanelController getCloudPanelController() { @SneakyThrows @Override public void initialize(URL location, ResourceBundle resources) { - config = ClientConfig.getConfig(); - fillElements(); + config = ClientConfig.getConfig(); rightTabClientController = (ClientPanelController) right_local_panel_view.getProperties().get("ctrl"); rightTabClientController.setMainSceneController(this); @@ -170,7 +161,6 @@ public void initialize(URL location, ResourceBundle resources) { cloudPanelController.setMainSceneController(this); cloudPanelController.init(); - Platform.runLater(this::checkConfig); } diff --git a/client/src/main/java/org/owpk/controller/ResizeHelper.java b/client/src/main/java/org/owpk/controller/ResizeHelper.java index 93210db..677daa5 100644 --- a/client/src/main/java/org/owpk/controller/ResizeHelper.java +++ b/client/src/main/java/org/owpk/controller/ResizeHelper.java @@ -13,11 +13,10 @@ /** - * Ресайзер для {@link Stage} + * Resize helper for {@link Stage} */ public class ResizeHelper { - public static void addResizeListener(Stage stage) { ResizeListener resizeListener = new ResizeListener(stage); stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); diff --git a/client/src/main/java/org/owpk/controller/TableViewController.java b/client/src/main/java/org/owpk/controller/TableViewController.java index d0a800e..d334608 100644 --- a/client/src/main/java/org/owpk/controller/TableViewController.java +++ b/client/src/main/java/org/owpk/controller/TableViewController.java @@ -16,7 +16,8 @@ import java.util.ResourceBundle; /** - * Контроллер {@link TableView} + * Controller {@link TableView}, creates a table that stores data of the {@link FileInfo} type, + * calculates the size for files and last modified date */ public class TableViewController implements Initializable { @FXML public TableView table; @@ -26,8 +27,8 @@ public class TableViewController implements Initializable { private TableColumn client_column_last_changed; /** - * Стилизатор для колонки Type в таблице - * Создает иконку для ячейки в таблице в зависимости от типа файла + * Type column + * Creates an icon for a cell in a table depending on the file type */ private static class TypeImageCellStylist extends TableCell { private ImageView image; @@ -50,9 +51,9 @@ protected void updateItem(FileInfo.FileType type, boolean empty) { } /** - * Стилизатор для колонки Size - * Расчитывет единицы измерения размера файла {@link #computeSize(Long)} - * создает ячейку в таблице + * Size column + * Calculate file size units {@link #computeSize (Long)} + * creates a cell in a table */ private static class SizeCellStylist extends TableCell { private final static String[] SIZES = {"B","KB","MB","GB","TB"}; diff --git a/client/src/main/java/org/owpk/controller/TreeViewController.java b/client/src/main/java/org/owpk/controller/TreeViewController.java index 5d58e2b..a3e9f6a 100644 --- a/client/src/main/java/org/owpk/controller/TreeViewController.java +++ b/client/src/main/java/org/owpk/controller/TreeViewController.java @@ -7,7 +7,7 @@ import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import org.owpk.app.Callback; +import org.owpk.util.Callback; import org.owpk.util.FileInfo; import java.io.File; @@ -15,11 +15,11 @@ import java.util.Objects; /** - * Контроллер {@link TreeView} слушает события "BranchExpandedEvent" {@link #setupListeners()}, - * по событию вычисляет абсолютный путь у текущего TreeItem на котором произошел вызов и добавляет к нему - * все папки которые удалось найти по этому пути {@link #populateItem(TreeItem)}, - * в свою очередь в каждой из этих папок проверяется наличие хотябы одной папки, если проверка пройдена, - * добавялется пустой узел заглушка для возможности вызова события "BranchExpandedEvent" + * The {@link TreeView} controller listens for "BranchExpandedEvent" events {@link #setupListeners()}, + * when event caused calculates the absolute path of the current TreeItem and adds to it + * all folders that could be found along this path {@link #populateItem (TreeItem)}, + * in turn, in each of these folders, the presence of at least one folder is checked, if the check is passed, + * an empty stub node is added to be able to raise the "BranchExpandedEvent" event */ public class TreeViewController { private static final String ROOT_NODE_NAME = "Local File System"; @@ -41,7 +41,7 @@ public static void setupTreeView(TreeView treeView) { } /** - * вызывается при первом запуске, доавляет TreeItem с названием дисков + * Invoking on app startup */ private static void fillTreeItems() { Arrays.stream(File.listRoots()) @@ -66,14 +66,16 @@ private static void expanded(TreeItem item) { } /** - * инициализирует слушателей на события BranchExpandedEvent и OnMouseClicked + * initializes listeners for the BranchExpandedEvent and OnMouseClicked events */ private static void setupListeners() { rootItem.addEventHandler(EventType.ROOT, event -> { if (event.getEventType().getName().equals("BranchExpandedEvent")) { - TreeItem item = (TreeItem) event.getSource(); - item.getChildren().clear(); - expanded(item); + Platform.runLater(() -> { + TreeItem item = (TreeItem) event.getSource(); + item.getChildren().clear(); + expanded(item); + }); } }); treeView.setOnMouseClicked(x -> { @@ -89,7 +91,7 @@ private static void setupListeners() { } /** - * Вычесляет абсолютный путь, собирает имена всех Parent узлов у TreeItem + * Computes the absolute path, collects the names of all Parent nodes from the TreeItem */ private static StringBuffer sb = new StringBuffer(); private static String getPath(TreeItem item) { diff --git a/client/src/main/java/org/owpk/controller/UserDialog.java b/client/src/main/java/org/owpk/controller/UserDialog.java index 66d2144..b32770e 100644 --- a/client/src/main/java/org/owpk/controller/UserDialog.java +++ b/client/src/main/java/org/owpk/controller/UserDialog.java @@ -1,34 +1,35 @@ package org.owpk.controller; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.control.TextInputDialog; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import org.owpk.IODataHandler.SignHandler; import org.owpk.app.ClientConfig; +import org.owpk.message.MessageType; +import org.owpk.message.UserInfo; +import org.owpk.network.IONetworkServiceImpl; import org.owpk.util.Config; +import java.io.IOException; import java.util.Optional; public class UserDialog { - public static void showDwnldDirSetupDialog(String message, ClientConfig config) { - TextInputDialog dialog = new TextInputDialog("walter"); - dialog.setTitle("Configure"); - dialog.setHeaderText(message); - dialog.setContentText("Please enter default download directory:"); - - Optional result = dialog.showAndWait(); - - result.ifPresent(name -> config.writeProperty( - Config.ConfigParameters.DOWNLOAD_DIR, result.get())); + public static void warningDialog(String header, String content) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle("Warning"); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); } - public static boolean confirmDialog(String folder) { + public static boolean confirmDialog(String header, String content) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Confirmation Dialog"); - alert.setHeaderText("Did not found specified download directory"); - alert.setContentText("Create default download folder? : \n" + folder); - + alert.setHeaderText(header); + alert.setContentText(content); Optional result = alert.showAndWait(); return result.get() == ButtonType.OK; } @@ -36,8 +37,54 @@ public static boolean confirmDialog(String folder) { public static void errorDialog(String msg) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle("Error"); - alert.setHeaderText("Look, an Error Dialog"); + alert.setHeaderText("Error"); + alert.setContentText(msg); + alert.showAndWait(); + } + + public static void infoDialog(String header, String msg) { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setHeaderText(header); alert.setContentText(msg); alert.showAndWait(); } + + public static void showConnectionParameters() { + Dialog dialog = new Dialog<>(); + dialog.setTitle("Connection configuration"); + dialog.setHeaderText("Please, input connection parameters: "); + + ButtonType okBtn = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); + ButtonType cancelBtn = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + + dialog.getDialogPane().getButtonTypes().setAll(okBtn, cancelBtn); + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + TextField host = new TextField(); + host.setPromptText("Host"); + TextField port = new TextField(); + port.setPromptText("Port"); + + grid.add(new Label("Host name:"), 0, 0); + grid.add(host, 1, 0); + grid.add(new Label("Port address:"), 0, 1); + grid.add(port, 1, 1); + + Node okButtonNode = dialog.getDialogPane().lookupButton(okBtn); + okButtonNode.setDisable(true); + + host.textProperty().addListener((observable, oldValue, newValue) -> okButtonNode.setDisable(newValue.trim().isEmpty())); + + dialog.getDialogPane().setContent(grid); + + Optional result = dialog.showAndWait(); + if (result.get() == okBtn) { + ClientConfig.getConfig().writeProperty(Config.ConfigParameters.HOST, host.getText()); + ClientConfig.getConfig().writeProperty(Config.ConfigParameters.PORT, port.getText()); + } + } } \ No newline at end of file diff --git a/client/src/main/java/org/owpk/network/IONetworkServiceImpl.java b/client/src/main/java/org/owpk/network/IONetworkServiceImpl.java index efde495..1cfd490 100644 --- a/client/src/main/java/org/owpk/network/IONetworkServiceImpl.java +++ b/client/src/main/java/org/owpk/network/IONetworkServiceImpl.java @@ -2,60 +2,112 @@ import io.netty.handler.codec.serialization.ObjectDecoderInputStream; import io.netty.handler.codec.serialization.ObjectEncoderOutputStream; -import org.owpk.app.Callback; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.owpk.IODataHandler.AbsHandler; +import org.owpk.IODataHandler.AuthHandler; +import org.owpk.IODataHandler.InputDataHandler; import org.owpk.app.ClientConfig; -import org.owpk.message.DataInfo; -import org.owpk.message.MessageType; -import org.owpk.message.Message; -import org.owpk.util.FileInfo; +import org.owpk.util.Callback; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.Socket; -import java.util.List; +import java.util.concurrent.ConcurrentLinkedDeque; +/** + * The {@link IONetworkServiceImpl} class creating a connection, + * by default uses parameters from the client.properties config file. + * Creates a pipeline of {@link AbsHandler} as listeners for messages from the server, + * the first time you connect, the default is to add {@link AuthHandler} as the first listener + * @see #connect() + * @see #executePipeline() + */ public class IONetworkServiceImpl implements NetworkServiceInt { + private static final IONetworkServiceImpl service = new IONetworkServiceImpl( + ClientConfig.getConfig().getHost(), ClientConfig.getConfig().getPort()); + private final String HOST; private final int PORT; private Socket socket; private ObjectDecoderInputStream in; private ObjectEncoderOutputStream out; + private InputDataHandler inputDataHandler; + private ConcurrentLinkedDeque pipeline; public IONetworkServiceImpl(String host, int port) { this.PORT = port; this.HOST = host; } + public static IONetworkServiceImpl getService() { + return service; + } + @Override - public void initDataHandler(Runnable r) { - Thread t = new Thread(r); - t.start(); + public void initHandlers(Callback... callback) throws IOException { + inputDataHandler = new InputDataHandler( + callback[0], + callback[1], + callback[2], + callback[3]); } @Override public String getName() { - return "localhost"; + return HOST; } + /** + * Creates a socket, InputStream, OutputStream, and pipeline, adds {@link AuthHandler} to the pipeline + */ @Override - public void connect() throws IOException { - socket = new Socket(HOST, PORT); - System.out.println("-:connected:"); - out = new ObjectEncoderOutputStream(socket.getOutputStream()); - in = new ObjectDecoderInputStream(socket.getInputStream()); + public void connect() throws IOException, InterruptedException, ClassNotFoundException { + socket = new Socket(HOST, PORT); + System.out.println("connected : " + socket.getRemoteSocketAddress()); + out = new ObjectEncoderOutputStream(socket.getOutputStream()); + in = new ObjectDecoderInputStream(socket.getInputStream()); + pipeline = new ConcurrentLinkedDeque<>(); + addHandlerToPipeline(new AuthHandler()); + executePipeline(); } @Override - public void disconnect() throws IOException { + public void disconnect() { if (socket != null) { - out.writeObject(new Message<>(MessageType.CLOSE, "")); - socket.close(); - out.close(); - out.flush(); - in.close(); - System.out.println("-:disconnected:"); + if (pipeline != null) clearPipeline(); + try { + socket.close(); + out.close(); + out.flush(); + in.close(); + System.out.println("disconnected: " + HOST); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void executePipeline() throws InterruptedException, IOException, ClassNotFoundException { + for (int i = 0; i < pipeline.size(); i++) { + pipeline.getLast().execute(); } } + public void addMainDataHandler() { + pipeline.add(inputDataHandler); + } + + public void addHandlerToPipeline(AbsHandler handler) { + pipeline.add(handler); + } + + public void clearPipeline() { + pipeline.forEach(x -> x.setHandlerOver(true)); + pipeline.clear(); + } + @Override public OutputStream getOut() { return out; diff --git a/client/src/main/java/org/owpk/network/InputDataHandler.java b/client/src/main/java/org/owpk/network/InputDataHandler.java deleted file mode 100644 index 447a725..0000000 --- a/client/src/main/java/org/owpk/network/InputDataHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.owpk.network; - -import io.netty.handler.codec.serialization.ObjectDecoderInputStream; -import org.owpk.app.Callback; -import org.owpk.app.ClientConfig; -import org.owpk.message.DataInfo; -import org.owpk.message.Message; -import org.owpk.util.FileInfo; -import org.owpk.util.FileUtility; - -import java.io.*; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -/** - * класс обработчик входных данных - */ -public class InputDataHandler implements Runnable { - - private final NetworkServiceInt networkServiceInt; - private final Callback serverStatusLabel; - private final Callback> tableViewCallback; - private final Callback progressBarCallback; - private final Callback refreshClientCallback; - private final Map files = new HashMap<>(); - - public InputDataHandler(NetworkServiceInt networkServiceInt, Callback... callbacks) throws IOException { - this.tableViewCallback = callbacks[0]; - this.serverStatusLabel = callbacks[1]; - this.progressBarCallback = callbacks[2]; - this.refreshClientCallback = callbacks[3]; - this.networkServiceInt = networkServiceInt; - } - - @Override - public void run() { - try { - System.out.println("-:input thread init:"); - Message msg; - ObjectDecoderInputStream in = (ObjectDecoderInputStream) networkServiceInt.getIn(); - while (true) { - if (in.available() > 0) { - msg = (Message) in.readObject(); - System.out.println(msg); - switch (msg.getType()) { - case DIR: - List dirList = (List) msg.getPayload(); - tableViewCallback.call(dirList); - break; - case DOWNLOAD: - download((DataInfo) msg); - break; - case OK: - case ERROR: - System.out.println(msg.getPayload()); - break; - case DEFAULT: - } - } - } - } catch (IOException | ClassNotFoundException e) { - System.out.println("-:oops server error:"); - serverStatusLabel.call("server error " + ClientConfig.getDefaultServer()); - e.printStackTrace(); - } finally { - try { - networkServiceInt.disconnect(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private boolean sessionIsOver(String fileName) { - return Arrays.stream(files.get(fileName)).allMatch(Objects::nonNull); - } - - private void download(DataInfo ms) throws IOException { - FileUtility.assembleChunkedFile(ms, files); - String fileName = ms.getFile(); - int chunkCount = ms.getChunkCount(); - DataInfo[] data = files.get(fileName); - if (data != null) { - long percentage = Arrays.stream(data) - .filter(Objects::nonNull) - .count(); - double count = (float) percentage / chunkCount; - progressBarCallback.call(count); - if (sessionIsOver(fileName)) { - final File f = new File( - ClientConfig.getConfig().getDownloadDirectory().toString() + "\\" + fileName); - FileUtility.writeBufferToFile(data, f); - progressBarCallback.call(0D); - serverStatusLabel.call("done"); - refreshClientCallback.call(ClientConfig.getConfig().getDownloadDirectory().toAbsolutePath()); - } - } - } -} diff --git a/client/src/main/java/org/owpk/network/NetworkServiceFactory.java b/client/src/main/java/org/owpk/network/NetworkServiceFactory.java index d932cab..e4ce3ea 100644 --- a/client/src/main/java/org/owpk/network/NetworkServiceFactory.java +++ b/client/src/main/java/org/owpk/network/NetworkServiceFactory.java @@ -1,22 +1,16 @@ package org.owpk.network; -import org.owpk.app.ClientConfig; - import java.util.HashMap; import java.util.Map; -/** - * Класс {@link IONetworkServiceImpl} создающий подключение, - * по умолчанию использует параметры из конфиг файла client.properties - */ public class NetworkServiceFactory { - private static final Map map; + private static final Map map; static { map = new HashMap<>(); - map.put("localhost", new IONetworkServiceImpl( - ClientConfig.getConfig().getHost(), ClientConfig.getConfig().getPort())); + map.put("localhost", IONetworkServiceImpl.getService()); + map.put("79.143.31.62", IONetworkServiceImpl.getService()); } - public static NetworkServiceInt getHandler(String server) { + public static NetworkServiceInt getService(String server) { return map.get(server); } } diff --git a/client/src/main/java/org/owpk/network/NetworkServiceInt.java b/client/src/main/java/org/owpk/network/NetworkServiceInt.java index 13d56c6..bef398a 100644 --- a/client/src/main/java/org/owpk/network/NetworkServiceInt.java +++ b/client/src/main/java/org/owpk/network/NetworkServiceInt.java @@ -1,18 +1,18 @@ package org.owpk.network; +import org.owpk.util.Callback; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public interface NetworkServiceInt { - void initDataHandler(Runnable r); + void initHandlers(Callback... callbacks) throws IOException; + void connect() throws IOException, ClassNotFoundException, InterruptedException; + void disconnect(); String getName(); - void connect() throws IOException; - void disconnect() throws IOException; - OutputStream getOut(); InputStream getIn(); - //TODO read command, read file, write command, write file } diff --git a/client/src/main/resources/view/client_panel.fxml b/client/src/main/resources/view/client_panel.fxml index faeb45a..3a61f32 100644 --- a/client/src/main/resources/view/client_panel.fxml +++ b/client/src/main/resources/view/client_panel.fxml @@ -51,6 +51,15 @@ + diff --git a/client/src/main/resources/view/cloud_panel.fxml b/client/src/main/resources/view/cloud_panel.fxml index aa06c8b..d25f82b 100644 --- a/client/src/main/resources/view/cloud_panel.fxml +++ b/client/src/main/resources/view/cloud_panel.fxml @@ -27,24 +27,6 @@ - -