Browse Source

fix plusieurs connexion au me^me utilisateur

Alexandre Gonzalvez 3 years ago
parent
commit
d318c5d34f

+ 68
- 46
Application/Clavardage/src/controller/Controller.java View File

@@ -31,9 +31,10 @@ public class Controller {
31 31
 	final static int portUDPlistening_remoteUsr2 = 31002; // TO REMOVE when we use broadcast
32 32
     final static int portUDPlistening_remoteUsr3 = 31003; // TO REMOVE when we use broadcast
33 33
     
34
+    public Boolean interfaceRunning;
34 35
 	/*** ATTRIBUTS ***/
35 36
 	protected LocalUser myUser;
36
-	protected Interface hisView;
37
+	protected Interface view;
37 38
 	private ListeningThreadUDP udp_connect_thread;
38 39
 	private ListeningThreadTCPConnection tcp_connect_thread;
39 40
 	private Historique histoire;
@@ -126,8 +127,8 @@ public class Controller {
126 127
 	public LocalUser getMyUser() {
127 128
 		return myUser;
128 129
 	}
129
-	public Interface getHisView() {
130
-		return hisView;
130
+	public Interface getview() {
131
+		return view;
131 132
 	}
132 133
 	public ListeningThreadUDP getUdp_connect_thread() {
133 134
 		return udp_connect_thread;
@@ -143,8 +144,8 @@ public class Controller {
143 144
 	public void setMyUser(LocalUser myUser) {
144 145
 		this.myUser = myUser;
145 146
 	}
146
-	public void setHisView(Interface hisView) {
147
-		this.hisView = hisView;
147
+	public void setview(Interface view) {
148
+		this.view = view;
148 149
 	}
149 150
 	public void setUdp_connect_thread(ListeningThreadUDP udp_connect_thread) {
150 151
 		this.udp_connect_thread = udp_connect_thread;
@@ -186,11 +187,11 @@ public class Controller {
186 187
 	public void changePseudo() throws IOException { 
187 188
 	    String oldPseudo = this.myUser.getPseudo(); //Saves the old one for comparison
188 189
 
189
-	    String tmpPseudo = hisView.PseudotextField.getText();  // Read user input
190
+	    String tmpPseudo = view.PseudotextField.getText();  // Read user input
190 191
 
191 192
 	    while(!(this.validatePseudo(tmpPseudo)) || tmpPseudo.equals(oldPseudo)) {
192
-	        hisView.Pseudolabel.setText("Already exists, enter another nickname. Your current username is: " + oldPseudo);  // Read user input
193
-	        tmpPseudo = hisView.PseudotextField.getText();  // Read user input
193
+	        view.Pseudolabel.setText("Already exists, enter another nickname. Your current username is: " + oldPseudo);  // Read user input
194
+	        tmpPseudo = view.PseudotextField.getText();  // Read user input
194 195
 	    }
195 196
 
196 197
 	    this.myUser.setPseudo(tmpPseudo);
@@ -328,29 +329,48 @@ public class Controller {
328 329
         dgramSocket.close();
329 330
 	}
330 331
 
331
-	public void openSession(Chat c) {
332
-		Socket link=null;
333
-		try {
334
-			System.out.println("("+this.myUser.getPseudo()+") Connecting to "+c.getRemoteUser().getPortTCP()+" of " + c.getRemoteUser().getPseudo());
335
-			link=new Socket(c.getRemoteUser().getAddIP(),c.getRemoteUser().getPortTCP());
336
-		}catch(IOException e) {
337
-			System.out.println("Error linking to TCP server of "+ c.getRemoteUser().getPortTCP());
338
-		}
332
+	/**
333
+	 * @parameters
334
+	 * 		@param chat : Chat 
335
+	 * <p>
336
+	 * 		Ajout ou mise à jour l'utilisateur distant dans notre liste d'utilisateur distant
337
+	 * </p>
338
+	 */
339
+	
340
+	
341
+	public void openSession(RemoteUser rm) {
342
+		myUser.addChats(rm);
343
+		Chat c = myUser.getChats().get(myUser.getChatIndexOf(rm));
339 344
 		
340
-		c.setSocket(link);
341
-        JOptionPane.showMessageDialog(null ,"New chat with "+c.getRemoteUser().getPseudo());
342
-        try {
343
-        	System.out.println(this.getHistory().retrieveMessage(getMyUser(), c.getRemoteUser()));
344
-		} catch (SQLException e) {
345
-			System.out.println("souci avec le retrieveMsg");
346
-			e.printStackTrace();
345
+		if(c.getActive()) {
346
+			JOptionPane.showMessageDialog(null ,"Already a session with "+c.getRemoteUser().getPseudo());
347
+		}
348
+		else {
349
+			Socket link=null;
350
+			try {
351
+				System.out.println("("+this.myUser.getPseudo()+") Connecting to "+c.getRemoteUser().getPortTCP()+" of " + c.getRemoteUser().getPseudo());
352
+				link=new Socket(c.getRemoteUser().getAddIP(),c.getRemoteUser().getPortTCP()/*, InetAddress.getLocalHost() ,myUser.getPortTCP()*/);
353
+			}catch(IOException e) {
354
+				System.out.println("Error linking to TCP server of "+ c.getRemoteUser().getPortTCP());
355
+			}
356
+			
357
+			c.setSocket(link);
358
+	        JOptionPane.showMessageDialog(null ,"New session with "+c.getRemoteUser().getPseudo());
359
+			// TODO Récupération de la conversation (historique)
360
+	        try {
361
+	        	System.out.println(this.getHistory().retrieveMessage(getMyUser(), c.getRemoteUser()));
362
+			} catch (SQLException e) {
363
+				System.out.println("souci avec le retrieveMsg");
364
+				e.printStackTrace();
365
+			}
366
+	        c.activate();
347 367
 		}
348
-		// TODO Récupération de la conversation (historique)
368
+		
349 369
 	}
350 370
 	
351 371
 	public void closeSession(Chat c) {
352
-		c.closeSocket();
353
-		// TODO Serait mieux d'enlever le chat de la liste de myUser
372
+		sendMsg(new Msg_Text(myUser.getAddIP(),"end"), c);
373
+		this.myUser.closeChat(c);
354 374
 	}
355 375
 	
356 376
 	public void sendMsg(Message msg,Chat c) {
@@ -377,8 +397,11 @@ public class Controller {
377 397
 		//System.out.println(message);
378 398
 		out.println(dateFormat.format(date));
379 399
 		out.println(message);
400
+		c.addMessage(msg);
401
+
380 402
 	}
381 403
 	
404
+	
382 405
 	// Plus utilisé
383 406
 	public void TCPmessage(int index) throws IOException {
384 407
 		Socket link=null;
@@ -468,12 +491,11 @@ public class Controller {
468 491
         
469 492
         if (index >= 0 && index<this.myUser.getRemoteUsersList().size()) {
470 493
         	
471
-            if(this.myUser.getIndexOf(this.myUser.getRemoteUsersList().get(index))==-1){
494
+            if(this.myUser.getChatIndexOf(this.myUser.getRemoteUsersList().get(index))==-1){
472 495
             	JOptionPane.showMessageDialog(null ,"User "+this.myUser.getRemoteUsersList().get(index).getPseudo()+" is already in chat with you");
473 496
             }
474 497
             else {
475
-            	Chat chat=this.myUser.addChats(this.myUser.getRemoteUsersList().get(index));
476
-            	this.openSession(chat);
498
+            	this.openSession(myUser.getRemoteUsersList().get(index));
477 499
             }
478 500
         }
479 501
         else {
@@ -499,21 +521,23 @@ public class Controller {
499 521
 		Controller ctr1 = new Controller(31011,31001,31021,histoire); 
500 522
 
501 523
 		/** Création de l'interface graphique **/ 
502
-		Boolean interfaceRunning = true;
503
-		ctr1.hisView=Interface.createAndShowGUI(ctr1);	
524
+		ctr1.interfaceRunning =true;
525
+		ctr1.view=Interface.createAndShowGUI(ctr1);	
504 526
 		
505 527
 		/** Simulation of a session **/
506 528
 		
507
-		// SELECT REMOTE USER
508
-		Chat chatwithrm0 = ctr1.myUser.addChats(ctr1.myUser.getRemoteUsersList().get(0));
529
+		
509 530
 		// AFFICHAGE REMOTE USER CHOISIE
510 531
 		System.out.println("("+ctr1.myUser.getPseudo()+" ) OPEN SESSION WITH "+ctr1.myUser.getRemoteUsersList().get(0).getPseudo());
532
+		// SELECTION DE L UTILISATEUR
533
+		RemoteUser rm0 = ctr1.myUser.getRemoteUsersList().get(0);
511 534
 		// OPEN SESSION
512
-		ctr1.openSession(chatwithrm0); 
535
+		ctr1.openSession(rm0); 
536
+		// RECUPERATION DE LA CONVERSATION
537
+		Chat chatwithrm0 = ctr1.myUser.getChats().get(ctr1.myUser.getChatIndexOf(rm0));
513 538
 		// SEND MESSAGE
514 539
 		ctr1.sendMsg(new Msg_Text(ctr1.myUser.getAddIP(),"test"), chatwithrm0);
515 540
 		// CLOSE SESSION
516
-		ctr1.sendMsg(new Msg_Text(ctr1.myUser.getAddIP(),"end"), chatwithrm0);
517 541
 		ctr1.closeSession(chatwithrm0); 
518 542
 		
519 543
 		
@@ -524,26 +548,24 @@ public class Controller {
524 548
 		//ctr1.changePseudo(); 
525 549
 					
526 550
 		
527
-		/*
528
-		while(interfaceRunning) {
529
-			
530
-		}*/
531 551
 		
532
-		/** Close thread and socket **/
533
-		// SLEEP 20 SEC
534
-		System.out.println("Sleep mode for 20 seconds ...");
535
-		Thread.sleep(20000);	
552
+		while(ctr1.interfaceRunning) {
553
+			//ctr1.view
554
+		}
555
+		
556
+		System.out.println("Fin de la boucle");
536 557
 		
558
+		/** Close thread and socket **/
537 559
 		// REMOTEUSER_1 - MIKE
538
-		ctr2.myUser.closeAllRemainingChatSocket();
560
+		ctr2.myUser.closeAllChat();
539 561
 		ctr2.tcp_connect_thread.close();
540 562
 		ctr2.udp_connect_thread.close();
541 563
 		// REMOTEUSER_2 - ALICE
542
-		ctr3.myUser.closeAllRemainingChatSocket();
564
+		ctr3.myUser.closeAllChat();
543 565
 		ctr3.tcp_connect_thread.close();
544 566
 		ctr3.udp_connect_thread.close();
545 567
 		// LOCAL USER
546
-		ctr1.myUser.closeAllRemainingChatSocket();
568
+		ctr1.myUser.closeAllChat();
547 569
 		ctr1.tcp_connect_thread.close();
548 570
 		ctr1.udp_connect_thread.close();
549 571
 		// AFFICHAGE

+ 34
- 8
Application/Clavardage/src/controller/ListeningThreadTCPChat.java View File

@@ -1,11 +1,15 @@
1 1
 package controller;
2 2
 
3
+import model.Chat;
3 4
 import model.LocalUser;
5
+import model.Msg_Text;
6
+import model.RemoteUser;
4 7
 
5 8
 import java.io.BufferedReader;
6 9
 import java.io.IOException;
7 10
 import java.io.InputStreamReader;
8 11
 import java.io.PrintWriter;
12
+import java.net.InetAddress;
9 13
 import java.net.Socket;
10 14
 import java.text.DateFormat;
11 15
 import java.text.SimpleDateFormat;
@@ -37,29 +41,51 @@ public class ListeningThreadTCPChat extends ListeningThread{
37 41
 	 * </p>
38 42
 	 */
39 43
 	public void run() {
44
+			/**** function variables ****/
40 45
 			BufferedReader in = null;
41 46
 			String msg = null;
42 47
 			String input;
43 48
 			String dateString;
44 49
 			Date date;
45 50
 		    Calendar date1=Calendar.getInstance();
46
-		    // TODO retrouver le chat lié à ce thread
47
-		    // Chat c = 
48
-		    // RemoteUser rm=c.getRemoteUser(); 
51
+
49 52
 		    
53
+		    Chat c = null;
54
+		    /************         Check rm user information            **********/
55
+		    /*
56
+		    RemoteUser rm = new RemoteUser("Unknown",this.socket.getInetAddress(),this.socket.getPort());
57
+		    int indexRM = controller.myUser.getActiveUserIndexOf(rm);
58
+		    // Check if rm is identifiable
59
+		    System.out.println(rm);
60
+		    if(indexRM!=-1) {
61
+		    	rm = controller.myUser.getRemoteUsersList().get(indexRM);
62
+		    	// Check if chat already created
63
+		    	int indexChat = controller.myUser.getChatIndexOf(rm);
64
+		    	if(indexChat!=-1) {
65
+		    		System.out.println("("+this.controller.myUser.getPseudo()+") Session déjà créer, on recupère le chat associé");
66
+		    		c = controller.myUser.getChats().get(indexChat);
67
+		    	}
68
+		    	else {
69
+		    		c = this.controller.myUser.addChats(rm);
70
+		    		this.controller.openSession(c);
71
+		    	}
72
+		    }
73
+		    else {
74
+		    	System.out.println("("+this.controller.myUser.getPseudo()+") Remote User unidentifiable => CLOSING CONNECTION");
75
+		    }
76
+		   */
77
+		    /*** listening tcp message from rm until session is close ***/
50 78
 			try {
51
-				System.out.println("("+this.controller.myUser.getPseudo()+") WAIT FOR NEW MESSAGE ");
79
+				System.out.println("("+this.controller.myUser.getPseudo()+") WAIT FOR NEW MESSAGE FROM ");//+rm.getPseudo());
52 80
 				in =new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
53 81
 			} catch (IOException e) {
54 82
 				// TODO Auto-generated catch block
55 83
 				e.printStackTrace();
56 84
 			}
57 85
 			try {
58
-				while (!(input=in.readLine()).equals("end")) {
86
+				while (!(input=in.readLine()).equals("end")/* && c.getActive()*/) {
59 87
 					System.out.println("("+this.controller.myUser.getPseudo()+") recoit : "+input);
60
-					// TODO save history (On doit choisir entre sauvegarder à la reception ou a l'emission) même appel dans controller.sendMsg()
61
-					//this.controller.getHistory().saveMessage(this.controller.myUser, rm,input ,dateString );
62
-					// TODO Update le chat avec ce nouveau message
88
+					//c.addMessage(new Msg_Text(rm.getAddIP(),input));
63 89
 					// TODO Prévenir l'interface de la réception d'un nouveau message
64 90
 
65 91
 				}

+ 10
- 1
Application/Clavardage/src/model/Chat.java View File

@@ -10,6 +10,7 @@ public class Chat {
10 10
 	private RemoteUser remoteUser;
11 11
 	private ArrayList<Message> messages = new ArrayList<Message>();
12 12
 	private Socket userSocket;
13
+	private Boolean active = false;
13 14
 	
14 15
 	/**
15 16
 	 *  Constructor of Chat (local)
@@ -34,6 +35,9 @@ public class Chat {
34 35
 	public Socket getUserSocket() {
35 36
 		return userSocket;
36 37
 	}
38
+	public Boolean getActive() {
39
+		return active;
40
+	}
37 41
 	
38 42
 	/*** SETTERS ***/
39 43
 	public void setRemoteUser(RemoteUser rm) {
@@ -45,8 +49,12 @@ public class Chat {
45 49
 	public void addMessage(Message msg) {
46 50
 		this.messages.add(msg);
47 51
 	}
52
+	public void activate() {
53
+		this.active = true;
54
+	}
48 55
 	
49
-	public void closeSocket() {
56
+	public void close() {
57
+		this.active = false;
50 58
 		try {
51 59
 			this.userSocket.close();
52 60
 		} catch (IOException e) {
@@ -54,4 +62,5 @@ public class Chat {
54 62
 		}
55 63
 		
56 64
 	}
65
+	
57 66
 }

+ 42
- 23
Application/Clavardage/src/model/LocalUser.java View File

@@ -78,40 +78,59 @@ public class LocalUser extends User{
78 78
 		}
79 79
 	}
80 80
 	
81
-	
82
-	/**
83
-	 * @parameters
84
-	 * 		@param remoteUser : remoteUser => référence de l'utilisateur distant
85
-	 * 		@param localUser_portTCP : int => le numéro de port TCP d'écoute de la conversation de l'utilisateur local
86
-	 * 		@param remoteUser_portTCP : int => le numéro de port TCP d'écoute de la conversation de l'utilisateur distant
87
-	 * <p>
88
-	 * 		Ajout ou mise à jour l'utilisateur distant dans notre liste d'utilisateur distant
89
-	 * </p>
90
-	 */
91
-	public Chat addChats(RemoteUser rm) {
81
+	public void addChats(RemoteUser rm) {
92 82
 		Chat newChat= new Chat(rm);
93
-		this.chats.add(newChat);
94
-		return newChat;
83
+
84
+		if(getChatIndexOf(rm)==-1) {
85
+			this.getChats().add(newChat);
86
+		}/*
87
+		else {
88
+			System.out.println("pb add chat, déjà un chat avec cet rm user");
89
+		}*/
95 90
 	}
91
+	
96 92
 
97
-	public int getIndexOf(RemoteUser remoteUser) {
98
-		int i=0;
99
-		Boolean found  = (this.chats.get(i).getRemoteUser() == remoteUser);
100
-		while(i<this.chats.size() && !found) {
101
-			i++;
102
-			found = (this.chats.get(i).getRemoteUser() == remoteUser);
93
+	public int getChatIndexOf(RemoteUser rm) {
94
+		int index=0;
95
+		
96
+		Boolean found = false;
97
+		while(index<this.chats.size() && !found) {
98
+			
99
+			found = (this.chats.get(index).getRemoteUser() == rm);
100
+			index++;
103 101
 		}
104 102
 		if(found) {
105
-			return i;
103
+			return index-1;
106 104
 		}
107 105
 		else {
108 106
 			return -1;
109 107
 		}
110 108
 	}
111 109
 	
112
-	public void closeAllRemainingChatSocket() {
113
-		for(int i=0;i<this.chats.size();i++) {
114
-			this.chats.get(i).closeSocket();
110
+	public int getActiveUserIndexOf(RemoteUser rm) {
111
+		int index = 0;
112
+		Boolean found = false;
113
+		while(index<this.remoteUsersList.size() && !found) { 
114
+			found = (this.remoteUsersList.get(index) == rm);
115
+			index++;
116
+		}
117
+		if(found) {
118
+			return index-1;
119
+		}
120
+		else {
121
+			return -1;
122
+		}
123
+	}
124
+	
125
+	public void closeChat(Chat c) {
126
+		this.chats.get(getChatIndexOf(c.getRemoteUser())).close();
127
+		this.chats.remove(c);
128
+	}
129
+	
130
+	public void closeAllChat() {
131
+		int length =this.chats.size();
132
+		for(int i=0;i<length;i++) {
133
+			this.closeChat(this.chats.get(0));
115 134
 		}
116 135
 	}
117 136
 }

+ 7
- 0
Application/Clavardage/src/model/RemoteUser.java View File

@@ -8,4 +8,11 @@ public class RemoteUser extends User{
8 8
 		super(pseudo, addIP, portTCP);
9 9
 	}
10 10
 
11
+	@Override
12
+	public String toString() {
13
+		return "RemoteUser [pseudo=" + pseudo + ", addIP=" + addIP + ", portTCP=" + portTCP + ", IPcode=" + IPcode
14
+				+ "]";
15
+	}
16
+
17
+	
11 18
 }

+ 1
- 0
Application/Clavardage/src/model/User.java View File

@@ -57,6 +57,7 @@ public abstract class User {
57 57
 	}
58 58
 
59 59
 	
60
+	
60 61
 	@Override
61 62
 	public boolean equals(Object obj) {
62 63
 		if (this == obj)

+ 17
- 13
Application/Clavardage/src/view/Interface.java View File

@@ -15,7 +15,7 @@ public class Interface extends JFrame implements ActionListener, WindowListener
15 15
 	 */
16 16
 	private static final long serialVersionUID = 1L; //JFrame stuff
17 17
 
18
-	public static Controller hisController;
18
+	public Controller controller;
19 19
 		
20 20
 	public JLabel Pseudolabel;
21 21
 	public JTextField PseudotextField;
@@ -31,7 +31,7 @@ public class Interface extends JFrame implements ActionListener, WindowListener
31 31
 	final static String LOOKANDFEEL = "System";
32 32
 	
33 33
 	public Interface(Controller controller) {
34
-		this.hisController = controller;
34
+		this.controller = controller;
35 35
 		this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
36 36
 		this.addWindowListener(new java.awt.event.WindowAdapter() {
37 37
 		    @Override
@@ -46,15 +46,15 @@ public class Interface extends JFrame implements ActionListener, WindowListener
46 46
 		PseudotextField = new JTextField();        //Pseudo setup
47 47
 		PseudotextField.setColumns(10);
48 48
 		PseudotextField.addActionListener(this);
49
-		Pseudolabel = new JLabel("Your current username is: " + hisController.getMyUser().getPseudo());
49
+		Pseudolabel = new JLabel("Your current username is: " + controller.getMyUser().getPseudo());
50 50
 		Pseudolabel.setLabelFor(PseudotextField);
51 51
 		
52 52
 		RemoteUserButton = new JButton("Click here to get Remote User list");          //Remote user list setup
53 53
 		RemoteUserButton.addActionListener(this);
54 54
 		//Convert the Userlist to Pseudotab
55
-		String[] pseudotab = new String[hisController.getMyUser().getRemoteUsersList().size()];
56
-		for(int i=0; i < hisController.getMyUser().getRemoteUsersList().size(); i++) {
57
-			pseudotab[i] = "(" + Integer.toString(i) + "): " + hisController.getMyUser().getRemoteUsersList().get(i).getPseudo();
55
+		String[] pseudotab = new String[controller.getMyUser().getRemoteUsersList().size()];
56
+		for(int i=0; i < controller.getMyUser().getRemoteUsersList().size(); i++) {
57
+			pseudotab[i] = "(" + Integer.toString(i) + "): " + controller.getMyUser().getRemoteUsersList().get(i).getPseudo();
58 58
 		}
59 59
 		RemoteUserbox = new JComboBox(pseudotab);
60 60
 		RemoteUserbox.setEditable(true);
@@ -98,7 +98,7 @@ public class Interface extends JFrame implements ActionListener, WindowListener
98 98
 			MessagetextField.setVisible(true);
99 99
 			Messagelabel.setVisible(true);
100 100
 			try {
101
-				hisController.changePseudo();
101
+				controller.changePseudo();
102 102
 			} catch (IOException e1) {
103 103
 				System.out.println("Error in change pseudo\n");
104 104
 				e1.printStackTrace();
@@ -113,7 +113,7 @@ public class Interface extends JFrame implements ActionListener, WindowListener
113 113
 		}else {
114 114
 			JComboBox cb = (JComboBox)e.getSource();          //Casts obscurs pour récupérer le numéro du user dans la liste
115 115
 			int selectedUsernb = Integer.parseInt(String.valueOf(((String) cb.getSelectedItem()).charAt(1)));
116
-			hisController.openSession(hisController.getMyUser().addChats(hisController.getMyUser().getRemoteUsersList().get(selectedUsernb)));
116
+			controller.openSession(controller.getMyUser().getRemoteUsersList().get(selectedUsernb));
117 117
 
118 118
 		}
119 119
 	}
@@ -169,19 +169,23 @@ public class Interface extends JFrame implements ActionListener, WindowListener
169 169
 		JFrame frame = new JFrame("Clavardage App");
170 170
 		frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
171 171
 		
172
+		
173
+		
174
+		Interface app = new Interface(controller);
175
+		Component contents = app.createComponents();
176
+		frame.getContentPane().add(contents, BorderLayout.CENTER);
177
+			
178
+		
172 179
 		frame.addWindowListener(new java.awt.event.WindowAdapter() {
173 180
 		    @Override
174 181
 		    public void windowClosing(java.awt.event.WindowEvent windowEvent) {
175 182
 		        System.out.println("GUI has been closed");
176
-		        hisController.interfaceRunning = false;
183
+		        app.controller.interfaceRunning = false;
184
+		       
177 185
 		        System.exit(0);
178 186
 		    }
179 187
 		});	
180 188
 		
181
-		Interface app = new Interface(controller);
182
-		Component contents = app.createComponents();
183
-		frame.getContentPane().add(contents, BorderLayout.CENTER);
184
-			
185 189
 		//Display the window.
186 190
 		frame.pack();
187 191
 		frame.setVisible(true);

Loading…
Cancel
Save