diff --git a/.classpath b/.classpath index 62fd5b9..317546f 100644 --- a/.classpath +++ b/.classpath @@ -1,11 +1,13 @@ - + + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index af1b6ea..af07d5f 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,12 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/lib/jdatepicker-1.3.4.jar b/lib/jdatepicker-1.3.4.jar new file mode 100644 index 0000000..a716259 Binary files /dev/null and b/lib/jdatepicker-1.3.4.jar differ diff --git a/lib/mysql-connector-j-8.4.0.jar b/lib/mysql-connector-j-8.4.0.jar new file mode 100644 index 0000000..8294fe0 Binary files /dev/null and b/lib/mysql-connector-j-8.4.0.jar differ diff --git a/src/client/AssignBox.java b/src/client/AssignBox.java new file mode 100644 index 0000000..26aac1d --- /dev/null +++ b/src/client/AssignBox.java @@ -0,0 +1,98 @@ +package client; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import task.TaskAssignment; + +public class AssignBox extends JFrame { + private JList taskList; + private DefaultListModel taskListModel; + private JTextArea taskDetails; + private JButton acceptButton; + private JButton rejectButton; + + public AssignBox() { + setTitle("Assigned Tasks"); + setSize(400, 300); + setLayout(new BorderLayout()); + + taskListModel = new DefaultListModel<>(); + taskList = new JList<>(taskListModel); + taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + taskList.addListSelectionListener(e -> displayTaskDetails()); + + taskDetails = new JTextArea(); + taskDetails.setEditable(false); + + acceptButton = new JButton("Accept"); + rejectButton = new JButton("Reject"); + + acceptButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + acceptTask(); + } + }); + + rejectButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + rejectTask(); + } + }); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(acceptButton); + buttonPanel.add(rejectButton); + + // Set preferred sizes for the list and detail components + taskList.setPreferredSize(new Dimension(200, 150)); // Adjust width and height as needed + taskDetails.setPreferredSize(new Dimension(200, 150)); // Adjust width and height as needed + + add(new JScrollPane(taskList), BorderLayout.CENTER); + add(new JScrollPane(taskDetails), BorderLayout.SOUTH); + add(buttonPanel, BorderLayout.NORTH); + } + + public void updateTaskList(List tasks) { + System.out.println("add task assignment"); + for (TaskAssignment task : tasks) { + taskListModel.addElement(task); + } + } + + private void displayTaskDetails() { + TaskAssignment selectedTask = taskList.getSelectedValue(); + if (selectedTask != null) { + taskDetails.setText("Name: " + selectedTask.getName() + + "\nStatus: " + selectedTask.getStatus() + + "\nDue Date: " + selectedTask.getYear() + "-" + selectedTask.getMonth() + "-" + selectedTask.getDay() + + "\nContent: " + selectedTask.getContent() + + "\nNotification Date: " + selectedTask.getNotificationYear() + "-" + selectedTask.getNotificationMonth() + "-" + selectedTask.getNotificationDay() + + "\nAssigned By: " + selectedTask.getTaskAssigner()); + } + } + + private void acceptTask() { + TaskAssignment selectedTask = taskList.getSelectedValue(); + if (selectedTask != null) { + Task task = new Task(selectedTask); + MainFrame.tasks.add(task); + MainFrame.tasksNumber++; + MainFrame.refreshMainFrame(); + taskListModel.removeElement(selectedTask); + taskDetails.setText(""); + } + } + + private void rejectTask() { + TaskAssignment selectedTask = taskList.getSelectedValue(); + if (selectedTask != null) { + taskListModel.removeElement(selectedTask); + taskDetails.setText(""); + } + } +} diff --git a/src/client/ChatForm.java b/src/client/ChatForm.java new file mode 100644 index 0000000..26436d0 --- /dev/null +++ b/src/client/ChatForm.java @@ -0,0 +1,37 @@ +package client; + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.event.*; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.io.BufferedReader; +import java.util.HashMap; + +public class ChatForm extends JFrame implements ActionListener { + private DataOutputStream serverOut; + private ClientInfo info; + private HashMap textTable = new HashMap(); + + public ChatForm(DataOutputStream serverOut, ClientInfo info) { + this.info = info; + this.serverOut = serverOut; + setupUI(); + } + + + public void actionPerformed(ActionEvent e) { + this.setVisible(true); + } + + private void setupUI() { + + } + + public void updateUI() { + + } + +} diff --git a/src/client/Client.java b/src/client/Client.java new file mode 100644 index 0000000..8faab14 --- /dev/null +++ b/src/client/Client.java @@ -0,0 +1,15 @@ +package client; + +public class Client { + public static final String SERVER_ADDRESS = "localhost"; + public static final int SERVER_PORT = 8000; + private static ClientInfo info = new ClientInfo(); + + public static void main(String[] args) { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + new LoginForm(info).setVisible(true); + } + }); + } +} diff --git a/src/client/ClientInfo.java b/src/client/ClientInfo.java new file mode 100644 index 0000000..bef49dd --- /dev/null +++ b/src/client/ClientInfo.java @@ -0,0 +1,18 @@ +package client; + +public class ClientInfo { + private String sessionId; + + public ClientInfo() { + this.sessionId = null; + } + + public String getSessionId() { + return this.sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + +} diff --git a/src/client/ConfirmWindow.java b/src/client/ConfirmWindow.java new file mode 100644 index 0000000..2f7b1af --- /dev/null +++ b/src/client/ConfirmWindow.java @@ -0,0 +1,54 @@ +package client; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; + +public class ConfirmWindow extends JFrame implements ActionListener{ + private BufferedReader serverIn; + private DataOutputStream serverOut; + private ClientInfo info; + private Socket socket; + + public ConfirmWindow(DataOutputStream serverOut, BufferedReader serverIn, ClientInfo info, Socket socket) { + this.serverIn = serverIn; + this.serverOut = serverOut; + this.info = info; + this.socket = socket; + + setSize(200, 100); + setLayout(new BorderLayout()); + JLabel confirmLabel = new JLabel("Are you sure you want to exit?"); + add(confirmLabel, BorderLayout.CENTER); + JButton exitButton = new JButton("Yes"); + exitButton.addActionListener(this); + add(exitButton, BorderLayout.SOUTH); + } + + public void actionPerformed(ActionEvent e) { + handleExit(); + System.exit(0); + } + + private synchronized void handleExit() { + try { + serverOut.writeBytes("EXIT " + info.getSessionId() + "\n"); + System.out.println("Closing connection..."); + socket.close(); + + // Dispose of the frame and exit the application + dispose(); + System.exit(0); + } catch (IOException e) { + + } + } +} \ No newline at end of file diff --git a/src/client/DateLabelFormatter.java b/src/client/DateLabelFormatter.java new file mode 100644 index 0000000..2d49c2d --- /dev/null +++ b/src/client/DateLabelFormatter.java @@ -0,0 +1,26 @@ +package client; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import javax.swing.JFormattedTextField.AbstractFormatter; + +public class DateLabelFormatter extends AbstractFormatter { + + private String datePattern = "yyyy-MM-dd"; + private SimpleDateFormat dateFormatter = new SimpleDateFormat(datePattern); + + @Override + public Object stringToValue(String text) throws ParseException { + return dateFormatter.parseObject(text); + } + + @Override + public String valueToString(Object value) throws ParseException { + if (value != null) { + Calendar cal = (Calendar) value; + return dateFormatter.format(cal.getTime()); + } + return ""; + } +} diff --git a/src/client/LoginForm.java b/src/client/LoginForm.java new file mode 100644 index 0000000..a00779f --- /dev/null +++ b/src/client/LoginForm.java @@ -0,0 +1,174 @@ +package client; + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.event.*; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.io.BufferedReader; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class LoginForm extends JFrame implements ActionListener { + private JTextField usernameField; + private JPasswordField passwordField; + private JButton loginButton; + private JButton registerButton; + private JButton exitButton; + private JLabel statusLabel; + private ClientInfo info; + private Socket socket; + private BufferedReader serverIn; + private DataOutputStream serverOut; + private ConfirmWindow confirmWindow; + + + public LoginForm(ClientInfo info) { + this.info = info; + setupUI(); + + try { + this.socket = new Socket(Client.SERVER_ADDRESS, Client.SERVER_PORT); + this.serverIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); + this.serverOut = new DataOutputStream(socket.getOutputStream()); + } catch (IOException e) { + e.printStackTrace(); + } + + confirmWindow = new ConfirmWindow(serverOut, serverIn, info, socket); + loginButton.addActionListener(this); + registerButton.addActionListener(this); + exitButton.addActionListener(this); + } + + private void setupUI() { + setTitle("Login"); + setSize(300, 200); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new CheckOnExit()); + setLocationRelativeTo(null); + setLayout(null); + + JLabel usernameLabel = new JLabel("Username:"); + usernameLabel.setBounds(10, 30, 80, 25); + add(usernameLabel); + + usernameField = new JTextField(); + usernameField.setBounds(100, 30, 160, 25); + add(usernameField); + + JLabel passwordLabel = new JLabel("Password:"); + passwordLabel.setBounds(10, 70, 80, 25); + add(passwordLabel); + + passwordField = new JPasswordField(); + passwordField.setBounds(100, 70, 160, 25); + add(passwordField); + + loginButton = new JButton("Login"); + loginButton.setBounds(9, 110, 80, 25); + add(loginButton); + + registerButton = new JButton("Register"); + registerButton.setBounds(103, 110, 80, 25); + add(registerButton); + + exitButton = new JButton("Exit"); + exitButton.setBounds(197, 110, 80, 25); + add(exitButton); + + + statusLabel = new JLabel(""); + statusLabel.setBounds(10, 140, 260, 25); + add(statusLabel); + } + + public void actionPerformed(ActionEvent e) { + if(e.getSource() == loginButton) { + handleLogin(); + }else if(e.getSource() == exitButton) { + this.confirmWindow.setVisible(true); + }else if(e.getSource() == registerButton) { + new RegistrationForm(serverOut, serverIn).setVisible(true); + } + } + + + private synchronized void handleLogin() { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + + try { + serverOut.writeBytes("LOGIN " + username + " " + password + "\n"); + + String response = serverIn.readLine(); + statusLabel.setText("Server response: " + response); + + if (response.startsWith("Login Successfully!")) { + // Schedule session refresh task + String sessionId = response.split(" ")[2]; + info.setSessionId(sessionId); + + if(info.getSessionId() != null) { + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + sendSessionRefresh(serverOut); + } + }, 0, 28, TimeUnit.MINUTES); + } + + + statusLabel.setText("Login successful"); + // Hide the login form and proceed to task management + usernameField.setText(""); + passwordField.setText(""); + usernameField.requestFocus(); + setVisible(false); + + new MainFrame(serverOut, serverIn, info, confirmWindow, this); + } else { + statusLabel.setText("Login failed: " + response.split(": ")[1]); + // Clear the text fields and focus on username field + usernameField.setText(""); + passwordField.setText(""); + usernameField.requestFocus(); + } + } catch (IOException e) { + statusLabel.setText("Error: " + e.getMessage()); + // Clear the text fields and focus on username field + usernameField.setText(""); + passwordField.setText(""); + usernameField.requestFocus(); + } + } + + + private class CheckOnExit implements WindowListener{ + public void windowClosing(WindowEvent e) { + confirmWindow.setVisible(true); + } + + public void windowOpened(WindowEvent e) {} + public void windowClosed(WindowEvent e) {} + public void windowIconified(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {} + public void windowActivated(WindowEvent e) {} + public void windowDeactivated(WindowEvent e) {} + + } + + + private synchronized void sendSessionRefresh(DataOutputStream serverOut) { + try { + serverOut.writeBytes("SESSION_REFRESH " + info.getSessionId() + "\n"); + System.out.println("Session refreshed."); + } catch (IOException e) { + System.out.println("Error refreshing session: " + e.getMessage()); + } + } +} diff --git a/src/client/MainFrame.java b/src/client/MainFrame.java new file mode 100644 index 0000000..76a86c5 --- /dev/null +++ b/src/client/MainFrame.java @@ -0,0 +1,368 @@ +package client; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import org.jdatepicker.impl.JDatePanelImpl; +import org.jdatepicker.impl.JDatePickerImpl; +import org.jdatepicker.impl.UtilDateModel; + +import javax.swing.*; +import java.util.Properties; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import parameter.*; + +public class MainFrame { + public static List tasks = new ArrayList<>(); + public static int tasksNumber = 0; // 目前的 task 數量 + private static Container cp; + private static JFrame frame; + private static JPanel taskPanel; + private BufferedReader serverIn; + private static DataOutputStream serverOut; + private static ClientInfo info; + private ChatForm chatForm; + private JButton chatSystem; + private static ServerResponse serverResponse; + private ConfirmWindow confirmWindow; + private LoginForm loginForm; + private AssignBox assignBox; + + public MainFrame(DataOutputStream serverOut, BufferedReader serverIn, ClientInfo info, ConfirmWindow confirmWindow, LoginForm loginForm) { + this.info = info; + this.serverIn = serverIn; + this.serverOut = serverOut; + this.confirmWindow = confirmWindow; + this.loginForm = loginForm; + chatForm = new ChatForm(serverOut, info); + createMainFrame(); + // open server message listener + serverResponse = new ServerResponse(); + serverResponse.setVisible(true); + this.assignBox = new AssignBox(); + new Thread(new MessageHandler(serverIn, this.serverResponse, this.frame, this.loginForm, this.assignBox)).start(); + //send data request to server... + initRequest(); + + // register listener + chatSystem.addActionListener(chatForm); + } + + private void createMainFrame() { + // 介面建立 + frame = new JFrame(); + frame.setSize(1200, 600); + frame.setLocation(100, 150); + frame.setTitle("Demo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + MainFrame.cp = frame.getContentPane(); + MainFrame.cp.setLayout(null); + + // Scrollable task panel + taskPanel = new JPanel(); + taskPanel.setLayout(new BoxLayout(taskPanel, BoxLayout.Y_AXIS)); // Use BoxLayout for dynamic content + + + // Create a JScrollPane to contain the task panel + JScrollPane scrollPane = new JScrollPane(taskPanel); + scrollPane.setBounds(20, 60, 930, 480); // Adjust size and position as needed + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Disable horizontal scroll + cp.add(scrollPane); + + + // 照狀態排序 + JButton sortByStatusButton = new JButton("Sort by Status"); + sortByStatusButton.setBounds(970, 20, 150, 25); + sortByStatusButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + TaskTracking.sortByStatus(); + for (int i = 0; i < 3; i++) { + System.out.println(MainFrame.tasks.get(i).getUserIDs()); + } + refreshMainFrame(); + } + }); + cp.add(sortByStatusButton); + + // 照日期排序 + JButton sortByDateButton = new JButton("Sort by Date"); + sortByDateButton.setBounds(970, 55, 150, 25); + sortByDateButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + TaskTracking.sortByDate(); + for (int i = 0; i < 3; i++) { + System.out.println(MainFrame.tasks.get(i).getUserIDs()); + } + refreshMainFrame(); + } + }); + cp.add(sortByDateButton); + + JButton createButton = new JButton("Create"); + createButton.setBounds(970, 90, 150, 25); + createButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + TaskManagement.Create(frame); + refreshMainFrame(); + } + }); + cp.add(createButton); + + // Assign Box button + JButton assignBoxButton = new JButton("Assign Box"); + assignBoxButton.setBounds(970, 125, 150, 25); + assignBoxButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + assignBox.setVisible(true); + } + }); + cp.add(assignBoxButton); + + // Save button + JButton SaveButton = new JButton("Save"); + SaveButton.setBounds(970, 160, 150, 25); + SaveButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + updateTask(serverOut, tasks); + } + }); + cp.add(SaveButton); + + // Logout button + JButton LogoutButton = new JButton("Logout"); + LogoutButton.setBounds(970, 195, 150, 25); + LogoutButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + logout(serverOut, info); + } + }); + cp.add(LogoutButton); + + // EXIT button + JButton ExitButton = new JButton("EXIT"); + ExitButton.setBounds(970, 230, 150, 25); + ExitButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + confirmWindow.setVisible(true); + } + }); + cp.add(ExitButton); + + + frame.setVisible(true); + } + + public static void refreshMainFrame() { + taskPanel.removeAll(); + + // Add labels row + JPanel labelPanel = new JPanel(); + labelPanel.setPreferredSize(new Dimension(800, 24)); + labelPanel.setMaximumSize(new Dimension(800, 24)); + labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.X_AXIS)); + + // Add empty label for "-" column + labelPanel.add(Box.createRigidArea(new Dimension(50, 24))); + + // Add labels + JLabel names = new JLabel("Task Names"); + names.setPreferredSize(new Dimension(150, 24)); + names.setMaximumSize(new Dimension(150, 24)); + names.setHorizontalAlignment(SwingConstants.LEADING); // Center align text + labelPanel.add(names); + + JLabel status = new JLabel("Status"); + status.setPreferredSize(new Dimension(150, 24)); + status.setMaximumSize(new Dimension(150, 24)); + status.setHorizontalAlignment(SwingConstants.LEADING); // Center align text + labelPanel.add(status); + + JLabel date = new JLabel("Dead Line"); + date.setPreferredSize(new Dimension(200, 24)); + date.setMaximumSize(new Dimension(200, 24)); + date.setHorizontalAlignment(SwingConstants.LEADING); // Center align text + labelPanel.add(date); + + taskPanel.add(labelPanel); + + // Add tasks to panel + for (int i = 0; i < tasksNumber; i++) { + final int finalI = i; + JPanel tp = new JPanel(); + tp.setPreferredSize(new Dimension(800, 35)); + tp.setMaximumSize(new Dimension(800, 35)); + tp.setLayout(new BoxLayout(tp, BoxLayout.X_AXIS)); + + // Delete task button + JButton deleteButton = new JButton("-"); + deleteButton.setPreferredSize(new Dimension(50, 30)); + deleteButton.setMaximumSize(new Dimension(50, 30)); + deleteButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + TaskManagement.Delete(finalI, frame); + refreshMainFrame(); + } + }); + tp.add(deleteButton); + + // Task name button + JButton button = new JButton(tasks.get(i).getName()); + button.setPreferredSize(new Dimension(150, 30)); + button.setMaximumSize(new Dimension(150, 30)); + button.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + TaskManagement.Edit(finalI); + refreshMainFrame(); + } + }); + tp.add(button); + + // Task status combo box + String[] choices = {"Done", "In Progress", "Not Started"}; + final JComboBox comboBox = new JComboBox<>(choices); + comboBox.setPreferredSize(new Dimension(150, 30)); + comboBox.setMaximumSize(new Dimension(150, 30)); + comboBox.setSelectedItem(tasks.get(i).getStatus()); + comboBox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + String selectedOption = (String) comboBox.getSelectedItem(); + tasks.get(finalI).setStatus(selectedOption); + } + } + }); + tp.add(comboBox); + + // Task date picker + UtilDateModel model = new UtilDateModel(); + Calendar defaultDate = Calendar.getInstance(); + defaultDate.set(tasks.get(i).getYear(), tasks.get(i).getMonth() - 1, tasks.get(i).getDay()); + model.setValue(defaultDate.getTime()); + + Properties p = new Properties(); + p.put("text.today", "Today"); + p.put("text.month", "Month"); + p.put("text.year", "Year"); + JDatePanelImpl datePanel = new JDatePanelImpl(model, p); + final JDatePickerImpl datePicker = new JDatePickerImpl(datePanel, new DateLabelFormatter()); + datePicker.setPreferredSize(new Dimension(200, 30)); + datePicker.setMaximumSize(new Dimension(200, 30)); + datePicker.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Date selectedDate = (Date) datePicker.getModel().getValue(); + if (selectedDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(selectedDate); + tasks.get(finalI).setYear(cal.get(Calendar.YEAR)); + tasks.get(finalI).setMonth(cal.get(Calendar.MONTH) + 1); + tasks.get(finalI).setDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }); + + JPanel panel = new JPanel(); + panel.setPreferredSize(new Dimension(200, 30)); + panel.setMaximumSize(new Dimension(200, 30)); + panel.add(datePicker); + tp.add(panel); + + // Task assign button + JButton buttonAssign = new JButton("Assign"); + buttonAssign.setPreferredSize(new Dimension(150, 30)); + buttonAssign.setMaximumSize(new Dimension(150, 30)); + buttonAssign.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + TaskManagement.Assigns(serverResponse, info, serverOut, tasks.get(finalI), finalI); + refreshMainFrame(); + } + }); + tp.add(buttonAssign); + + // Notification 按鈕的驚嘆號(作為通知用) + JLabel notificationLabel = new JLabel("!"); + notificationLabel.setPreferredSize(new Dimension(50, 30)); + notificationLabel.setMaximumSize(new Dimension(50, 30)); + notificationLabel.setVisible(false); + notificationLabel.setForeground(Color.RED); + tp.add(notificationLabel); + + // 判定當天是否有通知 + boolean hasNotificationToday = false; + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); + today.set(Calendar.MILLISECOND, 0); + + for (Task task : tasks) { + if (task.getNotificationYear() == today.get(Calendar.YEAR) && + task.getNotificationMonth() == today.get(Calendar.MONTH) + 1 && + task.getNotificationDay() == today.get(Calendar.DAY_OF_MONTH)) { + hasNotificationToday = true; + break; + } + } + + // 有通知就顯現驚嘆號 + if (hasNotificationToday) { + notificationLabel.setVisible(true); + } + + + taskPanel.add(tp); + } + + taskPanel.revalidate(); + taskPanel.repaint(); + } + + + private synchronized void initRequest() { + try { + serverOut.writeBytes("USER_INIT_DATA" + " " + info.getSessionId() + "\n"); + }catch(IOException e) { + System.out.println(e.getMessage()); + } + } + + private static synchronized void logout(DataOutputStream serverOut, ClientInfo info){ + serverResponse.init(); + serverResponse.setVisible(true); + try { + serverOut.writeBytes("LOGOUT" + " " + info.getSessionId() + "\n"); + info.setSessionId(null); + }catch(IOException e) { + + } + } + + private static synchronized void updateTask(DataOutputStream serverOut, List tasks){ + serverResponse.init(); + serverResponse.setVisible(true); + try { + StringBuilder taskDataBuilder = new StringBuilder(); + for(Task task : tasks) { + String taskData = task.toString(); + taskDataBuilder.append(taskData).append(";"); + } + serverOut.writeBytes("UPDATE_TASK" + " " + info.getSessionId() + " " + taskDataBuilder.toString() + "\n"); + }catch(IOException e) { + e.printStackTrace(); + } + } + + +} diff --git a/src/client/MessageHandler.java b/src/client/MessageHandler.java new file mode 100644 index 0000000..79b797b --- /dev/null +++ b/src/client/MessageHandler.java @@ -0,0 +1,175 @@ +package client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringTokenizer; +import javax.swing.JFrame; +import parameter.ServerInMsg; +import task.TaskAssignment; + +public class MessageHandler implements Runnable { + private BufferedReader serverIn; + private ServerResponse serverResponse; + private JFrame frame; + private JFrame loginForm; + private AssignBox assignBox; + private volatile boolean running = true; // Add a flag to control the loop + + public MessageHandler(BufferedReader serverIn, ServerResponse serverResponse, JFrame frame, JFrame loginForm, AssignBox assignBox) { + this.serverIn = serverIn; + this.serverResponse = serverResponse; + this.frame = frame; + this.loginForm = loginForm; + this.assignBox = assignBox; + } + + @Override + public void run() { + try { + String message; + while (running && (message = serverIn.readLine()) != null) { + System.out.println("received server response :" + message); + processMessage(message); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void processMessage(String message) { + StringTokenizer tokenizer = new StringTokenizer(message); + String commandType = tokenizer.nextToken(); + + switch (ServerInMsg.getEnum(commandType)) { + case RE_UPDATE_TASK: + handleUpdateReply(tokenizer); + break; + case RE_ASSIGN: + handleAssignReply(tokenizer); + break; + case RE_LOGOUT: + handleLogout(tokenizer); + break; + case INIT_TASK: + handleInit(tokenizer); + break; + case UPDATE_TASK: + handleTaskUpdate(tokenizer); + // handle task update + break; + case INIT_CHAT: + break; + case UPDATE_CHAT: + // handle chat update + break; + default: + break; + } + } + + private void handleInit(StringTokenizer tokenizer) { + if (tokenizer.countTokens() < 1) { + serverResponse.show("Invalid Command"); + return; + } + + String status = tokenizer.nextToken(); + if (status.equals("SUCCESS")) { + serverResponse.show("request initial data:" + status); + } else { + serverResponse.show(status + tokenizer.nextToken()); + } + + + + List tasks = new ArrayList<>(); + while (tokenizer.hasMoreTokens()) { + String taskData = tokenizer.nextToken(";"); + String[] fields = taskData.split("\\|"); + Task task = new Task( + fields[0], // name + fields[1], // status + Integer.parseInt(fields[2]), // year + Integer.parseInt(fields[3]), // month + Integer.parseInt(fields[4]), // day + fields[5], // content + Integer.parseInt(fields[6]), // notificationYear + Integer.parseInt(fields[7]), // notificationMonth + Integer.parseInt(fields[8]) // notificationDay + ); + if(fields.length > 9 && fields[9] != null) { + task.setUserIDs(new ArrayList<>(Arrays.asList(fields[9].split(",")))); + } + MainFrame.tasks.add(task); + } + + MainFrame.tasksNumber = MainFrame.tasks.size(); + MainFrame.refreshMainFrame(); + } + + private void handleUpdateReply(StringTokenizer tokenizer) { + String status = tokenizer.nextToken(); + if (status.equals("SUCCESS")) { + serverResponse.show("data are sucessfully update to database !\n"); + } else if (status.equals("FAIL")) { + serverResponse.show("data are faild to update to database !\n"); + } else { + serverResponse.show("session is invalid, please relogin....\n"); + } + } + + private void handleAssignReply(StringTokenizer tokenizer) { + String status = tokenizer.nextToken(); + if (status.equals("SUCCESS")) { + serverResponse.show("task assign process success\n"); + } else if (status.equals("FAIL")) { + if(tokenizer.hasMoreTokens()) { + serverResponse.show("task assign process fail " + tokenizer.nextToken("\n")); + }else { + serverResponse.show("task assign process fail\n"); + } + } else { + serverResponse.show("session is invalid, please relogin....\n"); + } + } + + private void handleLogout(StringTokenizer tokenizer) { + String status = tokenizer.nextToken(); + System.out.println(status); + if (status.equals("SUCCESS")) { + serverResponse.show("Logout successfully: go back to login interface..."); + MainFrame.tasks.clear(); + frame.getContentPane().removeAll(); + frame.dispose(); + loginForm.setVisible(true); + running = false; // Call to stop the thread + } else { + serverResponse.show("Invalid Request"); + } + } + + private void handleTaskUpdate(StringTokenizer tokenizer) { + List taskAssignments = new ArrayList<>(); + while(tokenizer.hasMoreTokens()) { + String taskInfo = tokenizer.nextToken(";"); + String[] fields = taskInfo.split("\\|"); + TaskAssignment task = new TaskAssignment(fields[0], // name + fields[1], // status + Integer.parseInt(fields[2]), // year + Integer.parseInt(fields[3]), // month + Integer.parseInt(fields[4]), // day + fields[5], // content + Integer.parseInt(fields[6]), // notificationYear + Integer.parseInt(fields[7]), // notificationMonth + Integer.parseInt(fields[8]), // notificationDay) + fields[9]); + taskAssignments.add(task); + } + assignBox.updateTaskList(taskAssignments); + serverResponse.show("There's new assignment, check out Assign Box !"); + } + +} diff --git a/src/client/Notification.java b/src/client/Notification.java new file mode 100644 index 0000000..3f8e9f9 --- /dev/null +++ b/src/client/Notification.java @@ -0,0 +1,97 @@ +package client; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +public class Notification { + + public static void notification() { + JFrame frame = new JFrame("Notifications"); + frame.setSize(800, 400); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + JPanel panel = new JPanel(new BorderLayout()); + + + JTable table = new JTable(); + DefaultTableModel model = new DefaultTableModel(new Object[]{"Name", "Status", "Task Date", "Content", "Notification Date"}, 0); + table.setModel(model); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); + today.set(Calendar.MILLISECOND, 0); + + List notifications = new ArrayList<>(); + + for (Task task : MainFrame.tasks) { + if (task.getNotificationYear() != 0 && task.getNotificationMonth() != 0 && task.getNotificationDay() != 0) { + Calendar notificationDate = Calendar.getInstance(); + notificationDate.set(task.getNotificationYear(), task.getNotificationMonth() - 1, task.getNotificationDay()); + notificationDate.set(Calendar.HOUR_OF_DAY, 0); + notificationDate.set(Calendar.MINUTE, 0); + notificationDate.set(Calendar.SECOND, 0); + notificationDate.set(Calendar.MILLISECOND, 0); + + if (!notificationDate.after(today)) { + notifications.add(task); + } + } + } + + + Collections.sort(notifications, (t1, t2) -> { + Calendar date1 = Calendar.getInstance(); + date1.set(t1.getNotificationYear(), t1.getNotificationMonth() - 1, t1.getNotificationDay()); + + Calendar date2 = Calendar.getInstance(); + date2.set(t2.getNotificationYear(), t2.getNotificationMonth() - 1, t2.getNotificationDay()); + + return date1.compareTo(date2); + }); + + for (Task task : notifications) { + + Calendar taskDate = Calendar.getInstance(); + taskDate.set(task.getYear(), task.getMonth() - 1, task.getDay()); + taskDate.set(Calendar.HOUR_OF_DAY, 0); + taskDate.set(Calendar.MINUTE, 0); + taskDate.set(Calendar.SECOND, 0); + taskDate.set(Calendar.MILLISECOND, 0); + + + model.addRow(new Object[]{task.getName(), task.getStatus(), sdf.format(convertToDate(taskDate)), task.getContent(), sdf.format(convertToDate(task.getNotificationYear(), task.getNotificationMonth(), task.getNotificationDay()))}); + } + + + JScrollPane scrollPane = new JScrollPane(table); + panel.add(scrollPane, BorderLayout.CENTER); + + + frame.add(panel); + frame.setVisible(true); + } + + + private static Date convertToDate(Calendar calendar) { + return calendar.getTime(); + } + + + private static Date convertToDate(int year, int month, int day) { + Calendar calendar = Calendar.getInstance(); + calendar.set(year, month - 1, day, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } +} diff --git a/src/client/RegistrationForm.java b/src/client/RegistrationForm.java new file mode 100644 index 0000000..6833a2e --- /dev/null +++ b/src/client/RegistrationForm.java @@ -0,0 +1,91 @@ +package client; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; + +public class RegistrationForm extends JFrame { + private JTextField usernameField; + private JPasswordField passwordField; + private JButton registerButton; + private JLabel statusLabel; + private DataOutputStream serverOut; + private BufferedReader serverIn; + + public RegistrationForm(DataOutputStream serverOut, BufferedReader serverIn) { + this.serverOut = serverOut; + this.serverIn = serverIn; + setupUI(); + } + + private void setupUI() { + setTitle("Registration"); + setSize(300, 200); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setLocationRelativeTo(null); + setLayout(null); + + JLabel usernameLabel = new JLabel("Username:"); + usernameLabel.setBounds(10, 30, 80, 25); + add(usernameLabel); + + usernameField = new JTextField(); + usernameField.setBounds(100, 30, 160, 25); + add(usernameField); + + JLabel passwordLabel = new JLabel("Password:"); + passwordLabel.setBounds(10, 70, 80, 25); + add(passwordLabel); + + passwordField = new JPasswordField(); + passwordField.setBounds(100, 70, 160, 25); + add(passwordField); + + registerButton = new JButton("Register"); + registerButton.setBounds(10, 110, 80, 25); + add(registerButton); + + statusLabel = new JLabel(""); + statusLabel.setBounds(10, 140, 260, 25); + add(statusLabel); + + registerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleRegistration(); + } + }); + } + + private void handleRegistration() { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + + try { + serverOut.writeBytes("REGISTRATION " + username + " " + password + "\n"); + + String response = serverIn.readLine(); + statusLabel.setText("Server response: " + response); + + if (response.startsWith("Registeration Sucessed")) { + statusLabel.setText("Registration successful"); + + dispose(); + } else { + statusLabel.setText(response); + usernameField.setText(""); + passwordField.setText(""); + usernameField.requestFocus(); + } + } catch (IOException e) { + statusLabel.setText("Error: " + e.getMessage()); + // Clear the text fields and focus on username field + usernameField.setText(""); + passwordField.setText(""); + usernameField.requestFocus(); + } + } +} diff --git a/src/client/ServerResponse.java b/src/client/ServerResponse.java new file mode 100644 index 0000000..bdf51a1 --- /dev/null +++ b/src/client/ServerResponse.java @@ -0,0 +1,38 @@ +package client; + +import java.awt.BorderLayout; + +import javax.swing.*; + +public class ServerResponse extends JFrame { + private JLabel status; + + public ServerResponse(){ + setSize(400, 200); + setLocationRelativeTo(null); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setLayout(new BorderLayout()); + + status = new JLabel("wait for server response....", SwingConstants.CENTER); + add(status, BorderLayout.CENTER); + } + + public void init() { + status.setText("wait for server response...."); + } + + public void show(String response) { + this.setVisible(true); + status.setText(response); + + try { + Thread.sleep(1500); + }catch(InterruptedException e) { + e.printStackTrace(); + } + + this.setVisible(false); + + } + +} diff --git a/src/client/Task.java b/src/client/Task.java new file mode 100644 index 0000000..a1cb544 --- /dev/null +++ b/src/client/Task.java @@ -0,0 +1,186 @@ +package client; + +import java.util.ArrayList; +import task.TaskAssignment; + +public class Task { + private String name; + private String status; + private int year; + private int month; + private int day; + private String content; + private ArrayList userIDs; + private int notificationYear; + private int notificationMonth; + private int notificationDay; + + public Task(String name, String status, int year, int month, int day, String content) { + this.name = name; + this.status = status; + this.year = year; + this.month = month; + this.day = day; + this.content = content; + this.notificationYear = 0; + this.notificationMonth = 0; + this.notificationDay = 0; + this.userIDs = new ArrayList<>(); + } + + public Task(String name, String status, int year, int month, int day, String content, int notificationYear, int notificationMonth, int notificationDay) { + this.name = name; + this.status = status; + this.year = year; + this.month = month; + this.day = day; + this.content = content; + this.notificationYear = notificationYear; + this.notificationMonth = notificationMonth; + this.notificationDay = notificationDay; + this.userIDs = new ArrayList<>(); + } + + public Task(String name, String status, int year, int month, int day, String content, int notificationYear, int notificationMonth, int notificationDay, ArrayList userIds) { + this.name = name; + this.status = status; + this.year = year; + this.month = month; + this.day = day; + this.content = content; + this.notificationYear = notificationYear; + this.notificationMonth = notificationMonth; + this.notificationDay = notificationDay; + this.userIDs = userIds; + } + + public Task(TaskAssignment taskAssignment) { + this.name = taskAssignment.getName(); + this.status = taskAssignment.getStatus(); + this.year = taskAssignment.getYear(); + this.month = taskAssignment.getMonth(); + this.day = taskAssignment.getDay(); + this.content = taskAssignment.getContent(); + this.notificationYear = taskAssignment.getNotificationYear(); + this.notificationMonth = taskAssignment.getNotificationMonth(); + this.notificationDay = taskAssignment.getNotificationDay(); + this.userIDs = new ArrayList<>(); + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public int getMonth() { + return month; + } + + public void setMonth(int month) { + this.month = month; + } + + public int getDay() { + return day; + } + + public void setDay(int day) { + this.day = day; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public ArrayList getUserIDs() { + return userIDs; + } + + public void setUserIDs(ArrayList userIDs) { + this.userIDs = userIDs; + } + + public void addUser(String userID) { + userIDs.add(userID); + } + + public void removeUser(String userID) { + userIDs.remove(userID); + } + + public int getNotificationYear() { + return notificationYear; + } + + public void setNotificationYear(int notificationYear) { + this.notificationYear = notificationYear; + } + + public int getNotificationMonth() { + return notificationMonth; + } + + public void setNotificationMonth(int notificationMonth) { + this.notificationMonth = notificationMonth; + } + + public int getNotificationDay() { + return notificationDay; + } + + public void setNotificationDay(int notificationDay) { + this.notificationDay = notificationDay; + } + + public String toString() { + String taskData = this.getName() + "|" + + this.getStatus() + "|" + + this.getYear() + "|" + + this.getMonth() + "|" + + this.getDay() + "|" + + this.getContent() + "|" + + this.getNotificationYear() + "|" + + this.getNotificationMonth() + "|" + + this.getNotificationDay() + "|" + + String.join(",", this.getUserIDs()); + return taskData; + } + + public String toAssignString(String assignedUser) { + String taskData = this.getName() + "|" + + this.getStatus() + "|" + + this.getYear() + "|" + + this.getMonth() + "|" + + this.getDay() + "|" + + this.getContent() + "|" + + this.getNotificationYear() + "|" + + this.getNotificationMonth() + "|" + + this.getNotificationDay() + "|" + + assignedUser; + return taskData; + } +} diff --git a/src/client/TaskManagement.java b/src/client/TaskManagement.java new file mode 100644 index 0000000..8a186a8 --- /dev/null +++ b/src/client/TaskManagement.java @@ -0,0 +1,224 @@ +package client; + +import javax.swing.JFrame; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.JTextArea; + +import org.jdatepicker.impl.JDatePanelImpl; +import org.jdatepicker.impl.JDatePickerImpl; +import org.jdatepicker.impl.UtilDateModel; + +import javax.swing.JComboBox; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.awt.Container; +import java.util.Calendar; +import java.util.Date; + + +public class TaskManagement { + + private static Container cp; + + public static void Create(JFrame frame) { // index 為當下所按的"+"的是第幾個tasks + + MainFrame.tasksNumber++; // Increase the task count + // 預設日期為創建task的當天日期 + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH) + 1; + int day = calendar.get(Calendar.DAY_OF_MONTH); + + MainFrame.tasks.add(0, new Task("New Task", "Not Started", year, month, day, "")); + + frame.revalidate(); + frame.repaint(); + } + + public static void Delete(int index, JFrame frame) { // 刪掉目前的task + // 刪除指定的task + MainFrame.tasks.remove(index); + MainFrame.tasksNumber--; // 總數量減少 + frame.revalidate(); + frame.repaint(); + } + + public static void Edit(int index) { + JFrame frame = new JFrame(); + frame.setSize(500, 500); + frame.setLocation(100, 150); + frame.setTitle("Edit"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + cp = frame.getContentPane(); + cp.setLayout(null); + + // task的name標籤 + JLabel nameLabel = new JLabel("Task Name:"); + nameLabel.setBounds(20, 20, 100, 25); + cp.add(nameLabel); + + // task的name資料(更改地方) + JTextField nameTextField = new JTextField(MainFrame.tasks.get(index).getName()); + nameTextField.setBounds(130, 20, 200, 25); + cp.add(nameTextField); + + // task的status標籤 + JLabel statusLabel = new JLabel("Status:"); + statusLabel.setBounds(20, 60, 100, 25); + cp.add(statusLabel); + + // task的status資料(更改地方) + String[] statusOptions = {"Done", "In Progress", "Not Started"}; + JComboBox statusComboBox = new JComboBox<>(statusOptions); + statusComboBox.setSelectedItem(MainFrame.tasks.get(index).getStatus()); + statusComboBox.setBounds(130, 60, 200, 25); + cp.add(statusComboBox); + + // task的date標籤 + JLabel dateLabel = new JLabel("Date:"); + dateLabel.setBounds(20, 100, 100, 25); + cp.add(dateLabel); + + // task的date資料(更改地方) + UtilDateModel model = new UtilDateModel(); + if (MainFrame.tasks.get(index).getYear() != 0 && MainFrame.tasks.get(index).getMonth() != 0 && MainFrame.tasks.get(index).getDay() != 0) { + Calendar defaultDate = Calendar.getInstance(); + defaultDate.set(MainFrame.tasks.get(index).getYear(), MainFrame.tasks.get(index).getMonth() - 1, MainFrame.tasks.get(index).getDay()); // 設置預設日期 + model.setValue(defaultDate.getTime()); + } + Properties p = new Properties(); + p.put("text.today", "Today"); + p.put("text.month", "Month"); + p.put("text.year", "Year"); + JDatePanelImpl datePanel = new JDatePanelImpl(model, p); + JDatePickerImpl datePicker = new JDatePickerImpl(datePanel, new DateLabelFormatter()); + JPanel datePanelContainer = new JPanel(); + datePanelContainer.add(datePicker); + datePanelContainer.setBounds(130, 100, 200, 30); + cp.add(datePanelContainer); + + // task的notification標籤 + JLabel notificationLabel = new JLabel("Notification:"); + notificationLabel.setBounds(20, 140, 100, 25); + cp.add(notificationLabel); + + // task的notification資料(更改地方) + UtilDateModel notificationModel = new UtilDateModel(); + if (MainFrame.tasks.get(index).getNotificationYear() != 0 && MainFrame.tasks.get(index).getNotificationMonth() != 0 && MainFrame.tasks.get(index).getNotificationDay() != 0) { + Calendar defaultNotification = Calendar.getInstance(); + defaultNotification.set(MainFrame.tasks.get(index).getNotificationYear(), MainFrame.tasks.get(index).getNotificationMonth() - 1, MainFrame.tasks.get(index).getNotificationDay()); // 設置預設notification日期 + notificationModel.setValue(defaultNotification.getTime()); + } + JDatePanelImpl notificationDatePanel = new JDatePanelImpl(notificationModel, p); + JDatePickerImpl notificationDatePicker = new JDatePickerImpl(notificationDatePanel, new DateLabelFormatter()); + JPanel notificationPanelContainer = new JPanel(); + notificationPanelContainer.add(notificationDatePicker); + notificationPanelContainer.setBounds(130, 140, 200, 30); + cp.add(notificationPanelContainer); + + // task的內容標籤 + JLabel contentLabel = new JLabel("Content:"); + contentLabel.setBounds(20, 180, 100, 25); + cp.add(contentLabel); + + // task的內容資料(更改地方) + JTextArea contentTextArea = new JTextArea(MainFrame.tasks.get(index).getContent()); + contentTextArea.setBounds(130, 180, 200, 100); + cp.add(contentTextArea); + + // 儲存 + JButton saveButton = new JButton("Save"); + saveButton.setBounds(100, 300, 100, 25); + saveButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + MainFrame.tasks.get(index).setName(nameTextField.getText()); + MainFrame.tasks.get(index).setStatus((String) statusComboBox.getSelectedItem()); + Date selectedDate = (Date) datePicker.getModel().getValue(); + if (selectedDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(selectedDate); + MainFrame.tasks.get(index).setYear(cal.get(Calendar.YEAR)); + MainFrame.tasks.get(index).setMonth(cal.get(Calendar.MONTH) + 1); + MainFrame.tasks.get(index).setDay(cal.get(Calendar.DAY_OF_MONTH)); + } + Date selectedNotification = (Date) notificationDatePicker.getModel().getValue(); + if (selectedNotification != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(selectedNotification); + MainFrame.tasks.get(index).setNotificationYear(cal.get(Calendar.YEAR)); + MainFrame.tasks.get(index).setNotificationMonth(cal.get(Calendar.MONTH) + 1); + MainFrame.tasks.get(index).setNotificationDay(cal.get(Calendar.DAY_OF_MONTH)); + } + MainFrame.tasks.get(index).setContent(contentTextArea.getText()); + frame.dispose(); + MainFrame.refreshMainFrame(); + } + }); + cp.add(saveButton); + // 取消 + JButton cancelButton = new JButton("Cancel"); + cancelButton.setBounds(220, 300, 100, 25); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + frame.dispose(); + } + }); + cp.add(cancelButton); + + frame.setVisible(true); + } + + public static void Assigns(ServerResponse serverResponse, ClientInfo info, DataOutputStream serverOut, final Task task, int index) { + final JFrame assignFrame = new JFrame(); + assignFrame.setSize(400, 400); + assignFrame.setLocation(200, 200); + assignFrame.setTitle("Assign Task"); + assignFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + cp = assignFrame.getContentPane(); + cp.setLayout(null); + + JLabel userLabel = new JLabel("Assign to User:"); + userLabel.setBounds(50, 20, 120, 25); + cp.add(userLabel); + + final JTextField userField = new JTextField(); + userField.setBounds(180, 20, 160, 25); + cp.add(userField); + + JButton assignButton = new JButton("Assign"); + assignButton.setBounds(150, 200, 80, 25); + assignButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String assignedUser = userField.getText(); + task.addUser(assignedUser); + JOptionPane.showMessageDialog(assignFrame, "Task assigned to " + assignedUser); + assignFrame.dispose(); + serverResponse.init(); + serverResponse.setVisible(true); + if (!assignedUser.isEmpty()) { + try { + String taskData = task.toAssignString(assignedUser); + serverOut.writeBytes("ASSIGN_TASK" + " " + info.getSessionId() + " " + taskData + "\n"); + }catch(IOException t) { + t.printStackTrace(); + } + MainFrame.refreshMainFrame(); + } else { + JOptionPane.showMessageDialog(assignFrame, "請輸入ID", "Error", JOptionPane.ERROR_MESSAGE); + } + } + }); + cp.add(assignButton); + + assignFrame.setVisible(true); + } +} diff --git a/src/client/TaskTracking.java b/src/client/TaskTracking.java new file mode 100644 index 0000000..c4d0dcb --- /dev/null +++ b/src/client/TaskTracking.java @@ -0,0 +1,94 @@ +package client; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class TaskTracking { + private static boolean ascendingOrder = true; // 用來確認當下的排序方法 + + public static void sortByStatus() { + List taskWithUsersList = new ArrayList<>(); + for (Task task : MainFrame.tasks) { + taskWithUsersList.add(new TaskWithUsers(task, task.getUserIDs())); + } + + Collections.sort(taskWithUsersList, new Comparator() { + + public int compare(TaskWithUsers twu1, TaskWithUsers twu2) { + String status1 = twu1.task.getStatus(); + String status2 = twu2.task.getStatus(); + if (status1 == null && status2 == null) { + return 0; + } else if (status1 == null) { + return 1; // 將null放在最後 + } else if (status2 == null) { + return -1; // 將null放在最後 + } else { + int result = status1.compareTo(status2); + return ascendingOrder ? result : -result; + } + } + }); + + for (int i = 0; i < MainFrame.tasks.size(); i++) { + MainFrame.tasks.set(i, taskWithUsersList.get(i).task); + MainFrame.tasks.get(i).setUserIDs(taskWithUsersList.get(i).userIDs); + } + + MainFrame.refreshMainFrame(); + ascendingOrder = !ascendingOrder; + } + + public static void sortByDate() { + List taskWithUsersList = new ArrayList<>(); + for (Task task : MainFrame.tasks) { + taskWithUsersList.add(new TaskWithUsers(task, task.getUserIDs())); + } + + Collections.sort(taskWithUsersList, new Comparator() { + + public int compare(TaskWithUsers twu1, TaskWithUsers twu2) { + try { + Task task1 = twu1.task; + Task task2 = twu2.task; + if (task1.getYear() == 0 || task1.getMonth() == 0 || task1.getDay() == 0) { + return 1; // 將null放在最後 + } else if (task2.getYear() == 0 || task2.getMonth() == 0 || task2.getDay() == 0) { + return -1; // 將null放在最後 + } else { + Calendar cal1 = Calendar.getInstance(); + cal1.set(task1.getYear(), task1.getMonth() - 1, task1.getDay()); + Calendar cal2 = Calendar.getInstance(); + cal2.set(task2.getYear(), task2.getMonth() - 1, task2.getDay()); + int result = cal1.compareTo(cal2); + return ascendingOrder ? result : -result; + } + } catch (Exception e) { + return 0; + } + } + }); + + for (int i = 0; i < MainFrame.tasks.size(); i++) { + MainFrame.tasks.set(i, taskWithUsersList.get(i).task); + MainFrame.tasks.get(i).setUserIDs(taskWithUsersList.get(i).userIDs); + } + + MainFrame.refreshMainFrame(); + ascendingOrder = !ascendingOrder; + } + + + + static class TaskWithUsers { + Task task; + ArrayList userIDs; + + TaskWithUsers(Task task, ArrayList userIDs) { + this.task = task; + this.userIDs = userIDs; + } + } +} \ No newline at end of file diff --git a/src/database/DatabaseUtil.java b/src/database/DatabaseUtil.java new file mode 100644 index 0000000..a91e7fb --- /dev/null +++ b/src/database/DatabaseUtil.java @@ -0,0 +1,311 @@ +package database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import client.Task; +import task.TaskAssignment; +import user.User; + + + +public class DatabaseUtil { + + private static final String URL = "jdbc:mysql://localhost:3306/UserInformation"; // where you save data + private static final String USER = "root"; + private static final String PASSWORD = "leo0909182463"; //user's password + + // get connect with database + private static Connection Connect() throws SQLException { + return DriverManager.getConnection(URL, USER, PASSWORD); + } + + + // Placeholder for storing users and tasks in-memory + public static List userList = new ArrayList<>(); + public static List assignData = new ArrayList<>(); + public static List taskData = new ArrayList<>(); + + + public synchronized static boolean AddTaskAssignments(String username, List taskAssignments) { + for(int i = 0;i < taskAssignments.size(); i++){ + String sql = "INSERT INTO UserData(name, username, creator, userIDs, status, year, month, day, content, notificationYear, notificationMonth, notificationDay) VALUES(? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + TaskAssignment assigntask = taskAssignments.get(i); + pstmt.setString(1, assigntask.getName()); + pstmt.setString(2, username); + pstmt.setString(3, assigntask.getTaskAssigner()); + pstmt.setString(4, ""); + pstmt.setString(5, assigntask.getStatus()); + pstmt.setInt(6, assigntask.getYear()); + pstmt.setInt(7, assigntask.getMonth()); + pstmt.setInt(8, assigntask.getDay()); + pstmt.setString(9, assigntask.getContent()); + pstmt.setInt(10, assigntask.getNotificationYear()); + pstmt.setInt(11, assigntask.getNotificationMonth()); + pstmt.setInt(12, assigntask.getNotificationDay()); + pstmt.executeUpdate(); + }catch(SQLException e){ + System.out.println(e.getMessage()); + return false; + } + } + return true; + } + + /** + * Fetches new task assignments from the database. + * @param username The username of the user whose data be requested + * @return List of new TaskAssignment objects for the user. + */ + public synchronized static List getNewTaskAssignments(String username) { + // Placeholder for actual database logic + List retuAssignments = new ArrayList<>(); + String sql = "SELECT id, name, creator, status, year, month, day, content, notificationYear, notificationMonth, notificationDay FROM UserData WHERE username = ? AND username != creator"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setString(1, username); + try(ResultSet rs = pstmt.executeQuery()){ + while(rs.next()){ + TaskAssignment task = new TaskAssignment(rs.getString("name"), rs.getString("status"), rs.getInt("year"), rs.getInt("month"), rs.getInt("day"), rs.getString("content"), rs.getInt("notificationYear"), rs.getInt("notificationMonth"), rs.getInt("notificationDay"), rs.getString("creator")); + retuAssignments.add(task); + String sql1 = "DELETE FROM UserData WHERE id = ?"; + try(Connection conn1 = Connect(); PreparedStatement pstmt1 = conn1.prepareStatement(sql1)){ + pstmt1.setInt(1, rs.getInt("id")); + pstmt1.executeUpdate(); + }catch(SQLException e1){ + System.out.println(e1.getMessage()); + } + } + } + }catch(SQLException e){ + System.out.println(e.getMessage()); + } + + return retuAssignments; + } + + /** + * Assigns a task to a single user. + * @param task The task to be assigned. + * @param username The username of the user to whom the task is assigned. + * @return true if the assignment was successful, false otherwise. + */ + public synchronized static boolean assignTaskToUser(TaskAssignment task, String username) { + // Placeholder for actual database logic + String sql = "INSERT INTO UserData(name, username, creator, userIDs, status, year, month, day, content, notificationYear, notificationMonth, notificationDay) VALUES(? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, task.getName()); + pstmt.setString(2, username); + pstmt.setString(3, task.getTaskAssigner()); + pstmt.setString(4, ""); + pstmt.setString(5, task.getStatus()); + pstmt.setInt(6, task.getYear()); + pstmt.setInt(7, task.getMonth()); + pstmt.setInt(8, task.getDay()); + pstmt.setString(9, task.getContent()); + pstmt.setInt(10, task.getNotificationYear()); + pstmt.setInt(11, task.getNotificationMonth()); + pstmt.setInt(12, task.getNotificationDay()); + pstmt.executeUpdate(); + return true; + } catch (SQLException e) { + System.out.println(e.getMessage()); + return false; + } + } + + /** + * Updates the tasks in the database for a specific user. + * @param username The username of the user whose tasks are being updated. + * @param tasks List of tasks to be updated. + * @return true if the update was successful, false otherwise. + */ + public synchronized static boolean updateTasksInDatabase(String username, List tasks) { + ArrayList taskIDs = new ArrayList<>(); + // Placeholder for actual database logic + String sql = "SELECT id FROM UserData WHERE username = ? AND username = creator"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setString(1, username); + try(ResultSet rs = pstmt.executeQuery()){ + while(rs.next()){ + taskIDs.add(rs.getInt("id")); + } + } + }catch(SQLException e){ + System.out.println(e.getMessage()); + return false; + } + for(int i = 0; i < Math.min(taskIDs.size(), tasks.size()); i++){ + Task task = tasks.get(i); + int taskID = taskIDs.get(i); + sql = "UPDATE UserData SET name = ?, status = ?, year = ?, month = ?, day = ?, content = ?, notificationYear = ?, notificationMonth = ?, notificationDay = ?, userIDs = ? WHERE id = ?"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setString(1, task.getName()); + pstmt.setString(2, task.getStatus()); + pstmt.setInt(3, task.getYear()); + pstmt.setInt(4, task.getMonth()); + pstmt.setInt(5, task.getDay()); + pstmt.setString(6, task.getContent()); + pstmt.setInt(7, task.getNotificationYear()); + pstmt.setInt(8, task.getNotificationMonth()); + pstmt.setInt(9, task.getNotificationDay()); + pstmt.setString(10, String.join(",", task.getUserIDs())); + pstmt.setInt(11, taskID); + pstmt.executeUpdate(); + }catch(SQLException e){ + System.out.println(e.getMessage()); + return false; + } + } + if(taskIDs.size() < tasks.size()){ + for(int i = taskIDs.size(); i < tasks.size(); i++){ + sql = "INSERT INTO UserData(name, username, creator, userIDs, status, year, month, day, content, notificationYear, notificationMonth, notificationDay) VALUES(? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + Task task = tasks.get(i); + pstmt.setString(1, task.getName()); + pstmt.setString(2, username); + pstmt.setString(3, username); + pstmt.setString(4, String.join(",", task.getUserIDs())); + pstmt.setString(5, task.getStatus()); + pstmt.setInt(6, task.getYear()); + pstmt.setInt(7, task.getMonth()); + pstmt.setInt(8, task.getDay()); + pstmt.setString(9, task.getContent()); + pstmt.setInt(10, task.getNotificationYear()); + pstmt.setInt(11, task.getNotificationMonth()); + pstmt.setInt(12, task.getNotificationDay()); + pstmt.executeUpdate(); + }catch(SQLException e){ + System.out.println(e.getMessage()); + return false; + } + } + }else if(taskIDs.size() > tasks.size()){ + for(int i = tasks.size(); i < taskIDs.size(); i++){ + sql = "DELETE FROM UserData WHERE id = ?"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setInt(1, taskIDs.get(i)); + pstmt.executeUpdate(); + }catch(SQLException e){ + System.out.println(e.getMessage()); + return false; + } + } + } + return true; + } + + /** + * Initializes the task data for a user from the database. + * @param username The username of the user. + * @return A string representation of the user's tasks. + */ + public synchronized static String initializeUserData(String username) { + // Placeholder for actual database logic + System.out.println("into init process"); + String output_string = ""; + String sql = "SELECT name, creator, userIDs, status, year, month, day, content, notificationYear, notificationMonth, notificationDay FROM UserData WHERE username = ? AND username = creator"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setString(1, username); + try(ResultSet rs = pstmt.executeQuery()){ + while(rs.next()){ + String userIDs = rs.getString("userIDs"); + String[] userIDsArray = userIDs.split(","); + ArrayList userIDsList = new ArrayList<>(Arrays.asList(userIDsArray)); + Task task = new Task(rs.getString("name"), rs.getString("status"), rs.getInt("year"), rs.getInt("month"), rs.getInt("day"), rs.getString("content"), rs.getInt("notificationYear"), rs.getInt("notificationMonth"), rs.getInt("notificationDay"), userIDsList); + output_string += (task.toString() + ";"); + } + } + }catch(SQLException e){ + System.out.println(e.getMessage()); + } + if (!output_string.isEmpty()) { + output_string = output_string.substring(0, output_string.length() - 1); + } + + return output_string; + } + + /** + * Finds a user by username in the database. + * @param username The username of the user to find. + * @return The User object if found, null otherwise. + */ + public synchronized static User findByUsername(String username) { + // Placeholder for actual database logic + String sql = "SELECT id, username, password FROM UserAccount WHERE username = ?"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + System.out.println("connectsuccess"); + pstmt.setString(1, username); + try(ResultSet rs = pstmt.executeQuery()){ + if(rs.next()){ + User user = new User(rs.getString("username"), rs.getString("password")); + return user; + } + } + }catch(SQLException e){ + System.out.println(e.getMessage()); + } + return null; + } + + /** + * Adds a user to the database. + * @param user The User object to add. + */ + public synchronized static void addUser(User user) { + // Placeholder for actual database logic + if(findByUsername(user.getUsername()) != null) { + System.out.println("usernmae have been used"); + return; + } + + String sql = "INSERT INTO UserAccount(username, password) VALUES(?, ?)"; + try(Connection conn = Connect(); PreparedStatement pstmt = conn.prepareStatement(sql)){ + pstmt.setString(1, user.getUsername()); + pstmt.setString(2, user.getPassword()); + pstmt.executeUpdate(); + }catch(SQLException e){ + System.out.println(e.getMessage()); + } + } + + /** + * Updates a user's password in the database. + * @param username The username of the user. + * @param newPassword The new password. + * @return true if the update was successful, false otherwise. + */ + public synchronized static boolean updatePassword(String username, String newPassword) { + // Placeholder for actual database logic + User user = findByUsername(username); + if (user != null) { + user.setPassword(newPassword); + return true; + } + return false; + } + + /** + * Updates a user's username in the database. + * @param oldUsername The current username of the user. + * @param newUsername The new username. + * @return true if the update was successful, false otherwise. + */ + public synchronized static boolean updateUsername(String oldUsername, String newUsername) { + // Placeholder for actual database logic + User user = findByUsername(oldUsername); + if (user != null && findByUsername(newUsername) == null) { + user.setUsername(newUsername); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/parameter/ErrorMsg.java b/src/parameter/ErrorMsg.java new file mode 100644 index 0000000..344b1bb --- /dev/null +++ b/src/parameter/ErrorMsg.java @@ -0,0 +1,8 @@ +package parameter; + +public enum ErrorMsg { + USER_NAME_BE_USED, + USER_NOT_EXIST, + PASSWORD_WRONG, + SUCCESS; +} diff --git a/src/parameter/ServerInMsg.java b/src/parameter/ServerInMsg.java new file mode 100644 index 0000000..7fc1f92 --- /dev/null +++ b/src/parameter/ServerInMsg.java @@ -0,0 +1,27 @@ +package parameter; + + +public enum ServerInMsg { + RE_UPDATE_TASK, + RE_ASSIGN, + RE_LOGOUT, + UPDATE_CHAT, + UPDATE_TASK, + INIT_CHAT, + INIT_TASK; + + + public static ServerInMsg getEnum(String value) { + if(value == null || value.length() < 1) { + return null; + } + + for(ServerInMsg cmd : values()) { + if(cmd.name().equalsIgnoreCase(value)) { + return cmd; + } + } + + return null; + } +} diff --git a/src/parameter/TaskCommand.java b/src/parameter/TaskCommand.java new file mode 100644 index 0000000..a8df70a --- /dev/null +++ b/src/parameter/TaskCommand.java @@ -0,0 +1,22 @@ +package parameter; + +public enum TaskCommand { + ASSIGN_TASK, + UPDATE_TASK, + USER_INIT_DATA; + + // Handle enum, in case invalid command lead to IllegalArgumentException + public static TaskCommand getEnum(String value) { + if (value == null || value.length() < 1) { + return null; + } + + for (TaskCommand cmd : values()) { + if (cmd.name().equalsIgnoreCase(value)) { + return cmd; + } + } + + return null; + } +} diff --git a/src/parameter/Command.java b/src/parameter/UserCommand.java similarity index 59% rename from src/parameter/Command.java rename to src/parameter/UserCommand.java index 14df853..6d625df 100644 --- a/src/parameter/Command.java +++ b/src/parameter/UserCommand.java @@ -1,19 +1,19 @@ package parameter; -// define command definition, used in ServiceThread class to declare hwo to action -public enum Command { +public enum UserCommand { LOGIN, + LOGOUT, REGISTRATION, - EXIT; + SESSION_REFRESH; // handle enum, in case invalid command lead IllegalArgumentException - public static Command getEnum(String value) { + public static UserCommand getEnum(String value) { if(value == null || value.length() < 1) { return null; } - for(Command cmd : values()) { + for(UserCommand cmd : values()) { if(cmd.name().equalsIgnoreCase(value)) { return cmd; } @@ -22,5 +22,4 @@ public static Command getEnum(String value) { return null; } - } diff --git a/src/server/Server.java b/src/server/Server.java index 0e7e8e0..f9b0258 100644 --- a/src/server/Server.java +++ b/src/server/Server.java @@ -1,28 +1,43 @@ package server; -import user.*; import java.io.*; import java.net.*; +import sessionManagement.SessionManager; + public class Server { public static final int PORT = 8000; - public static UserService userservice = new UserService(); public static void main(String[] args) { // open server socket + ServerSocket serversock = null; + try { - ServerSocket serversock = new ServerSocket(PORT); - System.out.println("Server end start..."); + serversock = new ServerSocket(PORT); + System.out.println("Server start..."); + while(true) { Socket connectionSock = serversock.accept(); ServiceThread service = new ServiceThread(connectionSock); Thread serviceThread = new Thread(service); - serviceThread.run(); + serviceThread.start(); } }catch(IOException e ) { System.out.println(e.getMessage()); + }finally { + // Close server socket in finally block to release resources + if (serversock != null) { + try { + serversock.close(); + } catch (IOException e) { + System.out.println("Error closing server socket: " + e.getMessage()); + } + } + + // shot down the scheduler + SessionManager.shutdownScheduler(); } } diff --git a/src/server/ServiceThread.java b/src/server/ServiceThread.java index d9ad5bb..3b92b53 100644 --- a/src/server/ServiceThread.java +++ b/src/server/ServiceThread.java @@ -1,50 +1,77 @@ package server; import user.*; -import parameter.Command; +import task.*; +import parameter.*; +import sessionManagement.SessionManager; import java.io.*; import java.net.*; import java.util.StringTokenizer; public class ServiceThread implements Runnable { private Socket socket; - private UserService userservice; - private String request; + private UserCommandHandler ucm; + private TaskCommandHandler tcm; + private TaskUpdateChecker taskUpdateChecker; public ServiceThread(Socket socket) { - this.socket = socket; - this.userservice = new UserService(); - } + this.socket = socket; + } + public void run() { try { BufferedReader clientIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); DataOutputStream clientOut = new DataOutputStream(socket.getOutputStream()); + taskUpdateChecker = new TaskUpdateChecker(clientOut); + this.tcm = new TaskCommandHandler(taskUpdateChecker); + this.ucm = new UserCommandHandler(taskUpdateChecker); + while(true) { + String request = clientIn.readLine(); + if (request == null) { + break; // Client disconnected + } + System.out.println("received client request :" + request); StringTokenizer tokenizer = new StringTokenizer(request); - Command command = Command.getEnum(tokenizer.nextToken()); + String commandType = tokenizer.nextToken(); + UserCommand userCommand = UserCommand.getEnum(commandType); + TaskCommand taskCommand = TaskCommand.getEnum(commandType); - switch(command) { - case LOGIN: - break; - case REGISTRATION: - break; - case EXIT: - // send back to client say service close... - break; - default: - break; + if(userCommand != null) { + ucm.handle(tokenizer, userCommand, clientOut); + }else if(taskCommand != null) { + tcm.handle(tokenizer, taskCommand, clientOut); + }else if("EXIT".equals(commandType)){ + clientOut.writeBytes("Service close\n"); + if(tokenizer.hasMoreTokens()) { + SessionManager.invalidateSession(tokenizer.nextToken()); + } + clientIn.close(); + socket.close(); + break; + }else { + clientOut.writeBytes("Invalid Command\n"); } - - + } }catch(IOException e) { System.out.println(e.getMessage()); - } - - - + }finally { + try { + if (taskUpdateChecker != null) { + taskUpdateChecker.stop(); + } + if (socket != null && !socket.isClosed()) { + socket.close(); + } + } catch (IOException e) { + System.out.println("Error closing socket: " + e.getMessage()); + } + } } + + } diff --git a/src/server/TaskUpdateChecker.java b/src/server/TaskUpdateChecker.java new file mode 100644 index 0000000..bbc4b40 --- /dev/null +++ b/src/server/TaskUpdateChecker.java @@ -0,0 +1,65 @@ +package server; + +import database.DatabaseUtil; +import task.TaskAssignment; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class TaskUpdateChecker implements Runnable { + private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private DataOutputStream clientOut; + private String username; + + public TaskUpdateChecker(DataOutputStream clientOut) { + this.clientOut = clientOut; + } + + public void start() { + if (scheduler.isShutdown() || scheduler.isTerminated()) { + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(this, 0, 20, TimeUnit.SECONDS); + }else { + scheduler.scheduleAtFixedRate(this, 0, 20, TimeUnit.SECONDS); + } + } + + public void stop() { + scheduler.shutdown(); + try { + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + } + } + + public void setUsername(String username) { + this.username = username; + } + + public synchronized void run () { + List newAssignments = DatabaseUtil.getNewTaskAssignments(username); + try { + if(newAssignments.size() > 0) { + StringBuilder taskDataBuilder = new StringBuilder(); + for(TaskAssignment taskAssignment : newAssignments) { + String taskData = taskAssignment.toString(); + taskDataBuilder.append(taskData).append(";"); + } + clientOut.writeBytes("UPDATE_TASK " + taskDataBuilder.toString() + "\n"); + } + }catch(IOException e) { + e.printStackTrace(); + } + + + + } + +} diff --git a/src/sessionManagement/SessionAutoUpdate.java b/src/sessionManagement/SessionAutoUpdate.java new file mode 100644 index 0000000..38356fa --- /dev/null +++ b/src/sessionManagement/SessionAutoUpdate.java @@ -0,0 +1,10 @@ +package sessionManagement; + +public class SessionAutoUpdate implements Runnable { + + public void run() { + + SessionManager.checkAndInvalidateExpiredSessions(); + + } +} diff --git a/src/sessionManagement/SessionManager.java b/src/sessionManagement/SessionManager.java new file mode 100644 index 0000000..1fde64d --- /dev/null +++ b/src/sessionManagement/SessionManager.java @@ -0,0 +1,102 @@ +package sessionManagement; + +import java.util.HashMap; +import java.util.concurrent.*; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public class SessionManager { + private static final long SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes + private static final Map sessions = new HashMap<>(); + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + static { + // Schedule the session cleanup task + scheduler.scheduleAtFixedRate(new SessionAutoUpdate(), 0, 30, TimeUnit.MINUTES); + } + + public static synchronized String createSession(String username) { + String sessionId = UUID.randomUUID().toString(); + long expirationTime = System.currentTimeMillis() + SESSION_TIMEOUT_MS; + Session session = new Session(sessionId, username, expirationTime); + sessions.put(sessionId, session); + return sessionId; + } + + public static synchronized boolean isValidSession(String sessionId) { + Session session = sessions.get(sessionId); + return session != null && System.currentTimeMillis() <= session.getExpiryTime(); + } + + public static synchronized String getUsername(String sessionId) { + Session session = sessions.get(sessionId); + return session != null ? session.getUsername() : null; + } + + public static synchronized void invalidateSession(String sessionId) { + if(sessionId == null) { + return; + } + sessions.remove(sessionId); + } + + public static synchronized void checkAndInvalidateExpiredSessions() { + long currentTime = System.currentTimeMillis(); + Iterator> iterator = sessions.entrySet().iterator(); + while(iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if(entry.getValue().getExpiryTime() <= currentTime) { + iterator.remove(); + } + } + } + + public static synchronized boolean sessionRefresh(String sessionId) { + Session session = sessions.get(sessionId); + if(session == null) { + System.out.println("session not exist"); + return false; + } + + session.resetExpiryTime(); + return true; + } + + private static class Session { + private final String sessionId; + private final String username; + private long expirationTime; + + public Session(String sessionId, String username, long expirationTime) { + this.sessionId = sessionId; + this.username = username; + this.expirationTime = expirationTime; + } + + public long getExpiryTime() { + return this.expirationTime; + } + + public void resetExpiryTime() { + this.expirationTime = System.currentTimeMillis() + SESSION_TIMEOUT_MS; + } + + public String getUsername() { + return username; + } + } + + + // call when server end, close the scheduler + public static void shutdownScheduler() { + scheduler.shutdown(); + try { + if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + } + } +} diff --git a/src/task/TaskAssignment.java b/src/task/TaskAssignment.java new file mode 100644 index 0000000..4e8fec7 --- /dev/null +++ b/src/task/TaskAssignment.java @@ -0,0 +1,30 @@ +package task; +import client.Task; + +public class TaskAssignment extends Task { + private String taskAssigner; + + public TaskAssignment(String name, String status, int year, int month, int day, String content, int notificationYear, int notificationMonth, int notificationDay, String taskAssigner) { + super(name, status, year, month, day, content, notificationYear, notificationMonth, notificationDay); + this.taskAssigner = taskAssigner; + } + + public String getTaskAssigner() { + return taskAssigner; + } + + @Override + public String toString() { + String taskData = this.getName() + "|" + + this.getStatus() + "|" + + this.getYear() + "|" + + this.getMonth() + "|" + + this.getDay() + "|" + + this.getContent() + "|" + + this.getNotificationYear() + "|" + + this.getNotificationMonth() + "|" + + this.getNotificationDay() + "|" + + this.taskAssigner; + return taskData; + } +} diff --git a/src/task/TaskCommandHandler.java b/src/task/TaskCommandHandler.java new file mode 100644 index 0000000..09c7c4f --- /dev/null +++ b/src/task/TaskCommandHandler.java @@ -0,0 +1,179 @@ +package task; +import user.*; +import server.TaskUpdateChecker; +import database.DatabaseUtil; +import client.Task; +import sessionManagement.SessionManager; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.StringTokenizer; +import parameter.TaskCommand; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class TaskCommandHandler { + private TaskUpdateChecker taskUpdateChecker; + + public TaskCommandHandler(TaskUpdateChecker taskUpdateChecker) { + this.taskUpdateChecker = taskUpdateChecker; + } + + public void handle(StringTokenizer tokenizer, TaskCommand command, DataOutputStream clientOut)throws IOException { + switch(command) { + case USER_INIT_DATA: + if(!handleInit(tokenizer, clientOut)) { + System.out.println("User init request data Faild"); + }else { + System.out.println("User init request data sucess"); + taskUpdateChecker.start(); + } + break; + case UPDATE_TASK: + if(!handleUpdate(tokenizer, clientOut)) { + System.out.println("Database update Faild"); + }else { + System.out.println("Database update sucess"); + } + break; + case ASSIGN_TASK: + if(!handleAssign(tokenizer, clientOut)) { + System.out.println("Assign process Faild"); + }else { + System.out.println("Assign process sucess"); + } + + break; + default: + clientOut.writeBytes("Invalid command\n"); + } + } + + private boolean handleInit(StringTokenizer tokenizer, DataOutputStream clientOut) throws IOException { + + if (tokenizer.countTokens() < 1) { + clientOut.writeBytes("Invalid Command\n"); + return false; + } + + String sessionId = tokenizer.nextToken(); + String username = SessionManager.getUsername(sessionId); + if(username == null) { + clientOut.writeBytes("INIT_TASK" + " Error" + " session is invalid, please relogin...." + "\n"); + return false; + } + + // Use user name to get the client's task list in the database + // This is a placeholder, replace with actual database update logic + + // how task list be send to server: + // each task's attribute is separated by "|" and each task is separate by ";" + // the attribute "user id list" is use "," to separate elements in the list + // replace "file" below as real string + String initData = DatabaseUtil.initializeUserData(username); + taskUpdateChecker.setUsername(username); + clientOut.writeBytes("INIT_TASK SUCCESS " + initData + "\n"); + return true; + } + + private boolean handleUpdate(StringTokenizer tokenizer, DataOutputStream clientOut) throws IOException { + if (tokenizer.countTokens() < 2) { + clientOut.writeBytes("Invalid Command\n"); + return false; + } + + String sessionId = tokenizer.nextToken(); + String username = SessionManager.getUsername(sessionId); + if(username == null) { + clientOut.writeBytes("RE_UPDATE_TASK Error\n"); + return false; + } + + List tasks = new ArrayList<>(); + while (tokenizer.hasMoreTokens()) { + String taskData = tokenizer.nextToken(";"); + String[] fields = taskData.split("\\|"); + Task task = new Task( + fields[0], // name + fields[1], // status + Integer.parseInt(fields[2]), // year + Integer.parseInt(fields[3]), // month + Integer.parseInt(fields[4]), // day + fields[5], // content + Integer.parseInt(fields[6]), // notificationYear + Integer.parseInt(fields[7]), // notificationMonth + Integer.parseInt(fields[8]) // notificationDay + ); + if(fields.length > 9 && fields[9] != null) { + task.setUserIDs(new ArrayList<>(Arrays.asList(fields[9].split(",")))); + } + + tasks.add(task); + } + boolean updateSuccess = DatabaseUtil.updateTasksInDatabase(username, tasks); + + if (updateSuccess) { + clientOut.writeBytes("RE_UPDATE_TASK SUCCESS\n"); + return true; + } else { + clientOut.writeBytes("RE_UPDATE_TASK FAIL\n"); + return false; + } + } + + private boolean handleAssign(StringTokenizer tokenizer, DataOutputStream clientOut) throws IOException { + if (tokenizer.countTokens() < 3) { + clientOut.writeBytes("Invalid Command\n"); + return false; + } + + String sessionId = tokenizer.nextToken(); + String username = SessionManager.getUsername(sessionId); + if(username == null) { + clientOut.writeBytes("RE_ASSIGN" + " Error\n"); + return false; + } + + // how task list be send by client: + // each task's attribute is separated by "|", the attribute "user id list" is use "," to separate elements in the list + // use user name map to database to update the client's task list in database : + String taskData = tokenizer.nextToken("\n"); + String[] fields = taskData.split("\\|"); + if (fields.length < 10) { + clientOut.writeBytes("RE_ASSIGN Error\n"); + return false; + } + // Create the task object + TaskAssignment task = new TaskAssignment( + fields[0], // name + fields[1], // status + Integer.parseInt(fields[2]), // year + Integer.parseInt(fields[3]), // month + Integer.parseInt(fields[4]), // day + fields[5], // content + Integer.parseInt(fields[6]), // notificationYear + Integer.parseInt(fields[7]), // notificationMonth + Integer.parseInt(fields[8]), // notificationDay + username // assigner + ); + + String target = fields[9]; + boolean assignSuccess = false; + if(DatabaseUtil.findByUsername(target) != null) { + assignSuccess = DatabaseUtil.assignTaskToUser(task, target); + }else { + clientOut.writeBytes("RE_ASSIGN FAIL user not exist\n"); + return false; + } + + if (assignSuccess) { + clientOut.writeBytes("RE_ASSIGN SUCCESS\n"); + return true; + } else { + clientOut.writeBytes("RE_ASSIGN FAIL\n"); + return false; + } + } + +} diff --git a/src/user/User.java b/src/user/User.java index e7408bb..f2db6fe 100644 --- a/src/user/User.java +++ b/src/user/User.java @@ -7,9 +7,9 @@ public class User { // init info public User(String username, String password){ - System.out.println("Enter user name:"); + System.out.println("user name:" + username); this.username = username; - System.out.println("Enter user password:"); + System.out.println("user password:" + password); this.password = password; } diff --git a/src/user/UserCommandHandler.java b/src/user/UserCommandHandler.java new file mode 100644 index 0000000..7c82762 --- /dev/null +++ b/src/user/UserCommandHandler.java @@ -0,0 +1,146 @@ +package user; +import parameter.*; +import sessionManagement.*; +import server.TaskUpdateChecker; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.StringTokenizer; + +public class UserCommandHandler { + private TaskUpdateChecker taskUpdateChecker; + + public UserCommandHandler(TaskUpdateChecker taskUpdateChecker) { + this.taskUpdateChecker = taskUpdateChecker; + } + + public void handle(StringTokenizer tokenizer, UserCommand command, DataOutputStream clientOut) throws IOException { + switch (command) { + case LOGIN: + if(!handleLogin(tokenizer, clientOut)) { + System.out.println("User Login Faild"); + }else { + System.out.println("User login sucess"); + } + break; + case LOGOUT: + if(!handleLogout(tokenizer, clientOut)) { + System.out.println("User Logout Faild"); + }else { + System.out.println("User Logout success"); + if(taskUpdateChecker != null) { + taskUpdateChecker.stop(); + } + } + break; + case REGISTRATION: + if(!handleRegistration(tokenizer, clientOut)) { + System.out.println("Registeration Faild"); + }else { + System.out.println("User Registeration success"); + } + break; + case SESSION_REFRESH: + // this command is automatically send from client end + if(!handleSessionRefresh(tokenizer)) { + System.out.println("session refresh Faild"); + } + + break; + default: + clientOut.writeBytes("Invalid command\n"); + } + } + + private boolean handleLogin(StringTokenizer tokenizer, DataOutputStream clientOut) { + try { + if (tokenizer.countTokens() < 2) { + clientOut.writeBytes("Invalid Command\n"); + return false; + } + + String username = tokenizer.nextToken(); + String password = tokenizer.nextToken(); + + switch (UserService.userLogin(username, password)) { + case SUCCESS: + String sessionId = SessionManager.createSession(username); + clientOut.writeBytes("Login Successfully! " + sessionId + "\n"); + return true; + case USER_NOT_EXIST: + clientOut.writeBytes("Login failed: user not exist\n"); + return false; + case PASSWORD_WRONG: + clientOut.writeBytes("Login failed: password wrong\n"); + return false; + default: + clientOut.writeBytes("Login failed: unexpected error\n"); + return false; + } + } catch (IOException e) { + System.out.println(e.getMessage()); + } + return false; + } + + + private boolean handleRegistration(StringTokenizer tokenizer, DataOutputStream clientOut) { + try { + if(tokenizer.countTokens() < 2) { + clientOut.writeBytes("Invalid Command"); + return false; + } + + String username = tokenizer.nextToken(); + String password = tokenizer.nextToken(); + + switch(UserService.registerUser(username, password)) { + case SUCCESS: + clientOut.writeBytes("Registeration Sucessed\n"); + return true; + case USER_NAME_BE_USED: + clientOut.writeBytes("Registeration Faild : username be used\n"); + return false; + default: + clientOut.writeBytes("Registeration Faild : unexpected error\n"); + return false; + } + + }catch(IOException e) { + System.out.println(e.getMessage()); + } + return false; + } + + private boolean handleLogout(StringTokenizer tokenizer, DataOutputStream clientOut) { + try { + if (tokenizer.countTokens() < 1) { + clientOut.writeBytes("RE_LOGOUT " + "ERROR\n"); + return false; + } + + String sessionId = tokenizer.nextToken(); + SessionManager.invalidateSession(sessionId); + clientOut.writeBytes("RE_LOGOUT " + "SUCCESS\n"); + return true; + } catch (IOException e) { + System.out.println(e.getMessage()); + } + + return false; + } + + private boolean handleSessionRefresh(StringTokenizer tokenizer) { + if(tokenizer.countTokens() < 1) { + return false; + } + + String sessionId = tokenizer.nextToken(); + if(SessionManager.sessionRefresh(sessionId)) { + return true; + } + + return false; + } + + +} diff --git a/src/user/UserService.java b/src/user/UserService.java index 2dbf597..21ce2da 100644 --- a/src/user/UserService.java +++ b/src/user/UserService.java @@ -1,49 +1,65 @@ package user; -import java.util.ArrayList; -import java.util.List; +import parameter.ErrorMsg; +import database.DatabaseUtil; public class UserService { - private List userList = new ArrayList<>(); // replace by database implement, CRUD - - // this method need to switch search database not in-memory access - public User findByUsername(String username) { - for(User user : userList) { - if(user.getUsername() == username) { - return user; - } - } - return null; - } - - // register info and store back to database - public boolean registerUser(String name, String password) { - if(findByUsername(name) != null) { - System.out.println("usernmae have been used"); - return false; - } - - String hashedPassword = HashUtil.passwordHash(password); - - User user = new User(name, hashedPassword); - userList.add(user); - return true; - // store back to databases - } - - // once login complete, create a session to the login user - public boolean userLogin(String username, String password) { - User user = findByUsername(username); - - if(user != null && HashUtil.checkPassword(password, user.getPassword())) { - // enter session part - return true; - }else if(user == null) { - System.out.println("user not exist"); - }else { - System.out.println("password wrong"); - } - return false; - } + // Register info and store back to database + public static ErrorMsg registerUser(String name, String password) { + if (DatabaseUtil.findByUsername(name) != null) { + System.out.println("Username has been used"); + return ErrorMsg.USER_NAME_BE_USED; + } + + String hashedPassword = HashUtil.passwordHash(password); + User user = new User(name, hashedPassword); + DatabaseUtil.addUser(user); + return ErrorMsg.SUCCESS; + } + + public static ErrorMsg userLogin(String username, String password) { + User user = DatabaseUtil.findByUsername(username); + if (user != null && HashUtil.checkPassword(password, user.getPassword())) { + // Enter session part + return ErrorMsg.SUCCESS; + } else if (user == null) { + System.out.println("User not exist"); + return ErrorMsg.USER_NOT_EXIST; + } else { + System.out.println("Password wrong"); + return ErrorMsg.PASSWORD_WRONG; + } + } + + public static boolean changePassword(String newPassword, String oldPassword, String username) { + User user = DatabaseUtil.findByUsername(username); + + if (user == null) { + System.out.println("User: " + username + " not exist"); + return false; + } + + if (user != null && HashUtil.checkPassword(oldPassword, user.getPassword())) { + return DatabaseUtil.updatePassword(username, newPassword); + } + + return false; + } + + public static boolean changeUsername(String username, String newName) { + User user = DatabaseUtil.findByUsername(username); + + if (user == null) { + System.out.println("User: " + username + " not exist"); + return false; + } + + if (DatabaseUtil.findByUsername(newName) != null) { + System.out.println("Name has been used"); + return false; + } else { + return DatabaseUtil.updateUsername(username, newName); + } + } }