Updated copyright notice
[pithos] / web_client / src / gr / grnet / pithos / web / client / FileUploadDialog.java
1 /*
2  * Copyright (c) 2011 Greek Research and Technology Network
3  */
4 package gr.grnet.pithos.web.client;
5
6 import gr.grnet.pithos.web.client.rest.GetCommand;
7 import gr.grnet.pithos.web.client.rest.PostCommand;
8 import gr.grnet.pithos.web.client.rest.RestCommand;
9 import gr.grnet.pithos.web.client.rest.RestException;
10 import gr.grnet.pithos.web.client.rest.resource.FileResource;
11 import gr.grnet.pithos.web.client.rest.resource.FolderResource;
12 import gr.grnet.pithos.web.client.rest.resource.RestResourceWrapper;
13 import gr.grnet.pithos.web.client.rest.resource.UploadStatusResource;
14
15 import java.util.ArrayList;
16 import java.util.List;
17
18 import com.google.gwt.core.client.GWT;
19 import com.google.gwt.dom.client.NativeEvent;
20 import com.google.gwt.event.dom.client.ClickEvent;
21 import com.google.gwt.event.dom.client.ClickHandler;
22 import com.google.gwt.event.dom.client.KeyCodes;
23 import com.google.gwt.http.client.URL;
24 import com.google.gwt.json.client.JSONObject;
25 import com.google.gwt.json.client.JSONString;
26 import com.google.gwt.user.client.DeferredCommand;
27 import com.google.gwt.user.client.Event.NativePreviewEvent;
28 import com.google.gwt.user.client.Timer;
29 import com.google.gwt.user.client.ui.Button;
30 import com.google.gwt.user.client.ui.DialogBox;
31 import com.google.gwt.user.client.ui.FileUpload;
32 import com.google.gwt.user.client.ui.FormPanel;
33 import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
34 import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
35 import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
36 import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
37 import com.google.gwt.user.client.ui.Grid;
38 import com.google.gwt.user.client.ui.HTML;
39 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
40 import com.google.gwt.user.client.ui.Hidden;
41 import com.google.gwt.user.client.ui.HorizontalPanel;
42 import com.google.gwt.user.client.ui.Label;
43 import com.google.gwt.user.client.ui.VerticalPanel;
44
45 /**
46  * The 'File upload' dialog box implementation.
47  */
48 public class FileUploadDialog extends DialogBox implements Updateable {
49
50         protected int prgBarInterval = 1500;
51
52         private ProgressBar progressBar;
53
54         protected RepeatingTimer repeater = new RepeatingTimer(this, prgBarInterval);
55
56         public static final boolean DONE = true;
57
58         /**
59          * The Form element that performs the file upload.
60          */
61         private final FormPanel form = new FormPanel();
62
63         private final FileUpload upload = new FileUpload();
64
65         protected final Label filenameLabel = new Label("");
66
67         protected List<FileResource> files;
68
69         protected boolean cancelEvent = false;
70
71         protected String fileNameToUse;
72
73         protected FolderResource folder;
74
75         /**
76          * The widget's constructor.
77          */
78         public FileUploadDialog() {
79                 // Set the dialog's caption.
80                 setText("File upload");
81                 setAnimationEnabled(true);
82                 // Since we're going to add a FileUpload widget, we'll need to set the
83                 // form to use the POST method, and multipart MIME encoding.
84                 form.setEncoding(FormPanel.ENCODING_MULTIPART);
85                 form.setMethod(FormPanel.METHOD_POST);
86
87                 // Create a panel to hold all of the form widgets.
88                 VerticalPanel panel = new VerticalPanel();
89                 form.setWidget(panel);
90                 final HTML info = new HTML("You may select a file to upload. Install" +
91                                 " <a href='http://gears.google.com/' target='_blank'>Google " +
92                                 "Gears</a><br> for uploading multiple files simultaneously.");
93                 info.addStyleName("pithos-uploadNote");
94                 panel.add(info);
95                 final Hidden date = new Hidden("Date", "");
96                 panel.add(date);
97                 final Hidden auth = new Hidden("Authorization", "");
98                 panel.add(auth);
99                 // Add an informative label with the folder name.
100                 Object selection = GSS.get().getTreeView().getSelection();
101                 folder = ((RestResourceWrapper) selection).getResource();
102                 upload.setName("file");
103                 filenameLabel.setText("");
104                 filenameLabel.setVisible(false);
105                 filenameLabel.setStyleName("props-labels");
106                 HorizontalPanel fileUloadPanel = new HorizontalPanel();
107                 fileUloadPanel.add(filenameLabel);
108                 fileUloadPanel.add(upload);
109                 upload.getElement().setId("fileUploadDiallog.uploadPanel");
110                 Grid generalTable = new Grid(2, 2);
111                 generalTable.setText(0, 0, "Folder");
112                 generalTable.setText(1, 0, "File");
113                 generalTable.setText(0, 1, folder.getName());
114                 generalTable.setWidget(1, 1, fileUloadPanel);
115                 generalTable.getCellFormatter().setStyleName(0, 0, "props-labels");
116                 generalTable.getCellFormatter().setStyleName(1, 0, "props-labels");
117                 generalTable.getCellFormatter().setStyleName(0, 1, "props-values");
118                 generalTable.getCellFormatter().setStyleName(1, 1, "props-values");
119                 generalTable.setCellSpacing(4);
120
121                 panel.add(generalTable);
122
123                 // Create a panel to hold the buttons.
124                 HorizontalPanel buttons = new HorizontalPanel();
125
126                 // Create the 'upload' button, along with a listener that submits the
127                 // form.
128                 final Button submit = new Button("Upload", new ClickHandler() {
129                         @Override
130                         public void onClick(ClickEvent event) {
131                                 prepareAndSubmit();
132                         }
133                 });
134                 submit.getElement().setId("fileUploadDialog.button.upload");
135                 buttons.add(submit);
136                 buttons.setCellHorizontalAlignment(submit, HasHorizontalAlignment.ALIGN_CENTER);
137                 // Create the 'Cancel' button, along with a listener that hides the
138                 // dialog when the button is clicked.
139                 final Button cancel = new Button("Cancel", new ClickHandler() {
140                         @Override
141                         public void onClick(ClickEvent event) {
142                                 repeater.finish();
143                                 hide();
144                         }
145                 });
146                 cancel.getElement().setId("fileUploadDialog.button.cancel");
147                 buttons.add(cancel);
148                 buttons.setCellHorizontalAlignment(cancel, HasHorizontalAlignment.ALIGN_CENTER);
149                 buttons.setSpacing(8);
150                 buttons.addStyleName("pithos-DialogBox");
151
152                 // Add an event handler to the form.
153                 form.addSubmitHandler(new SubmitHandler() {
154
155                         @Override
156                         public void onSubmit(SubmitEvent event) {
157                                 GSS app = GSS.get();
158                                 // This event is fired just before the form is submitted. We can
159                                 // take this opportunity to perform validation.
160                                 if (upload.getFilename().length() == 0) {
161                                         app.displayError("You must select a file!");
162                                         event.cancel();
163                                         hide();
164                                 } else {
165
166                                         canContinue();
167                                         GWT.log("Cancel:" + cancelEvent, null);
168                                         if (cancelEvent) {
169                                                 cancelEvent = false;
170                                                 app.displayError("The specified file name already exists in this folder");
171                                                 event.cancel();
172                                                 hide();
173                                         } else {
174
175                                                 fileNameToUse = getFilename(upload.getFilename());
176                                                 String apath;
177                                                 FileResource selectedFile = getFileForName(fileNameToUse);
178                                                 if (selectedFile == null ) {
179                                                         //we are going to create a file
180                                                         apath = folder.getUri();
181                                                         if (!apath.endsWith("/"))
182                                                                 apath = apath + "/";
183                                                         apath = apath + encodeComponent(fileNameToUse);
184                                                 } else
185                                                         apath = selectedFile.getUri();
186                                                 form.setAction(apath);
187                                                 String dateString = RestCommand.getDate();
188                                                 String resource = apath.substring(app.getApiPath().length() - 1, apath.length());
189                                                 String sig = RestCommand.calculateSig("POST", dateString, resource, RestCommand.base64decode(app.getToken()));
190                                                 date.setValue(dateString);
191                                                 auth.setValue(app.getCurrentUserResource().getUsername() + " " + sig);
192                                                 GWT.log("FolderPATH:" + folder.getUri(), null);
193                                                 submit.setEnabled(false);
194                                                 upload.setVisible(false);
195                                                 filenameLabel.setText(fileNameToUse);
196                                                 filenameLabel.setVisible(true);
197                                                 repeater.start();
198                                                 progressBar.setVisible(true);
199                                         }
200                                 }
201
202                         }
203                 });
204                 form.addSubmitCompleteHandler(new SubmitCompleteHandler() {
205
206                         @Override
207                         public void onSubmitComplete(SubmitCompleteEvent event) {
208                                 // When the form submission is successfully completed, this
209                                 // event is fired. Assuming the service returned a response
210                                 // of type text/html, we can get the result text here (see
211                                 // the FormPanel documentation for further explanation).
212                                 String results = event.getResults();
213
214                                 // Unfortunately the results are never empty, even in
215                                 // the absense of errors, so we have to check for '<pre></pre>'.
216                                 if (!results.equalsIgnoreCase("<pre></pre>")) {
217                                         GWT.log(results, null);
218                                         GSS.get().displayError(results);
219                                 }
220                                 progressBar.setProgress(100);
221                                 cancelUpload();
222                                 GSS.get().getTreeView().updateNode(GSS.get().getTreeView().getSelection());
223                                 GSS.get().getStatusPanel().updateStats();
224
225                         }
226                 });
227
228
229                 panel.add(buttons);
230                 progressBar = new ProgressBar(50, ProgressBar.SHOW_TIME_REMAINING);
231                 panel.add(progressBar);
232                 progressBar.setVisible(false);
233                 panel.setCellHorizontalAlignment(buttons, HasHorizontalAlignment.ALIGN_CENTER);
234                 panel.setCellHorizontalAlignment(progressBar, HasHorizontalAlignment.ALIGN_CENTER);
235                 panel.addStyleName("pithos-DialogBox");
236                 addStyleName("pithos-DialogBox");
237                 setWidget(form);
238         }
239
240         @Override
241         protected void onPreviewNativeEvent(NativePreviewEvent preview) {
242                 super.onPreviewNativeEvent(preview);
243
244                 NativeEvent evt = preview.getNativeEvent();
245                 if (evt.getType().equals("keydown"))
246                         // Use the popup's key preview hooks to close the dialog when either
247                         // enter or escape is pressed.
248                         switch (evt.getKeyCode()) {
249                                 case KeyCodes.KEY_ENTER:
250                                         prepareAndSubmit();
251                                         break;
252                                 case KeyCodes.KEY_ESCAPE:
253                                         cancelUpload();
254                                         break;
255                         }
256         }
257
258
259
260         /**
261          * Cancels the file upload.
262          */
263         private void cancelUpload() {
264                 repeater.finish();
265                 hide();
266         }
267
268         /**
269          * Make any last minute checks and start the upload.
270          */
271         public void prepareAndSubmit() {
272                 final String fname = getFilename(upload.getFilename());
273                 if (getFileForName(fname) == null) {
274                         //we are going to create a file, so we check to see if there is a trashed file with the same name
275                         FileResource same = null;
276                         for (FileResource fres : folder.getFiles())
277                                 if (fres.isDeleted() && fres.getName().equals(fname))
278                                         same = fres;
279                         if (same == null)
280                                 form.submit();
281                         else {
282                                 final FileResource sameFile = same;
283                                 GWT.log("Same deleted file", null);
284                                 ConfirmationDialog confirm = new ConfirmationDialog("A file with " +
285                                                 "the same name exists in the trash. If you continue,<br/>the trashed " +
286                                                 "file  '" + fname + "' will be renamed automatically for you.", "Continue") {
287
288                                         @Override
289                                         public void cancel() {
290                                                 FileUploadDialog.this.hide();
291                                         }
292
293                                         @Override
294                                         public void confirm() {
295                                                 updateTrashedFile(getBackupFilename(fname), sameFile);
296                                         }
297
298                                 };
299                                 confirm.center();
300                         }
301                 }
302                 else {
303                         // We are going to update an existing file, so show a confirmation dialog.
304                         ConfirmationDialog confirm = new ConfirmationDialog("Are you sure " +
305                                         "you want to update " + fname + "?", "Update") {
306
307                                 @Override
308                                 public void cancel() {
309                                         FileUploadDialog.this.hide();
310                                 }
311
312                                 @Override
313                                 public void confirm() {
314                                         form.submit();
315                                 }
316
317                         };
318                         confirm.center();
319                 }
320         }
321
322         /**
323          * Returns the file name from a potential full path argument. Apparently IE
324          * insists on sending the full path name of a file when uploading, forcing
325          * us to trim the extra path info. Since this is only observed on Windows we
326          * get to check for a single path separator value.
327          *
328          * @param name the potentially full path name of a file
329          * @return the file name without extra path information
330          */
331         protected String getFilename(String name) {
332                 int pathSepIndex = name.lastIndexOf("\\");
333                 if (pathSepIndex == -1) {
334                         pathSepIndex = name.lastIndexOf("/");
335                         if (pathSepIndex == -1)
336                                 return name;
337                 }
338                 return name.substring(pathSepIndex + 1);
339         }
340
341         /**
342          * Check whether the file name exists in selected folder.
343          *
344          * @return
345          */
346         private boolean canContinue() {
347                 if (files == null)
348                         return false;
349                 String fileName = getFilename(upload.getFilename());
350                 if (getFileForName(fileName) == null) {
351                         // For file creation, check to see if the file already exists.
352                         GWT.log("filename to upload:" + fileName, null);
353                         for (FileResource dto : files) {
354                                 GWT.log("Check:" + dto.getName() + "/" + fileName, null);
355                                 if (!dto.isDeleted() && dto.getName().equals(fileName)) {
356                                         cancelEvent = true;
357                                         return true;
358                                 }
359                         }
360                 }
361                 return true;
362         }
363
364         class RepeatingTimer extends Timer {
365
366                 private Updateable updateable;
367
368                 private int interval = 1500;
369
370                 private boolean running = true;
371
372                 RepeatingTimer(Updateable _updateable, int _interval) {
373                         updateable = _updateable;
374                         interval = _interval;
375                 }
376
377                 @Override
378                 public void run() {
379                         updateable.update();
380                 }
381
382                 public void start() {
383                         running = true;
384
385                         scheduleRepeating(interval);
386                 }
387
388                 public void finish() {
389                         running = false;
390                         cancel();
391                 }
392
393                 public int getInterval() {
394                         return interval;
395                 }
396
397                 public void setInterval(int anInterval) {
398                         if (interval != anInterval) {
399                                 interval = anInterval;
400                                 if (running) {
401                                         finish();
402                                         start();
403                                 }
404                         }
405                 }
406         }
407
408         @Override
409         public void update() {
410                 String apath = folder.getUri();
411                 if (!apath.endsWith("/"))
412                         apath = apath + "/";
413                 apath = apath + encodeComponent(fileNameToUse) + "?progress=" + encodeComponent(fileNameToUse);
414                 GetCommand eg = new GetCommand<UploadStatusResource>(UploadStatusResource.class, apath, false, null) {
415
416                         @Override
417                         public void onComplete() {
418                                 UploadStatusResource res = getResult();
419                                 progressBar.setProgress(res.percent());
420                         }
421
422                         @Override
423                         public void onError(Throwable t) {
424                                 GWT.log("", t);
425                         }
426
427                 };
428                 DeferredCommand.addCommand(eg);
429         }
430
431         protected String getBackupFilename(String filename) {
432                 List<FileResource> filesInSameFolder = new ArrayList<FileResource>();
433                 for (FileResource deleted : folder.getFiles())
434                         if (deleted.isDeleted())
435                                 filesInSameFolder.add(deleted);
436                 int i = 1;
437                 for (FileResource same : filesInSameFolder)
438                         if (same.getName().startsWith(filename)) {
439                                 String toCheck = same.getName().substring(filename.length(), same.getName().length());
440                                 if (toCheck.startsWith(" ")) {
441                                         int test = -1;
442                                         try {
443                                                 test = Integer.valueOf(toCheck.replace(" ", ""));
444                                         } catch (NumberFormatException e) {
445                                                 // Do nothing since string is not a number.
446                                         }
447                                         if (test >= i)
448                                                 i = test + 1;
449                                 }
450                         }
451
452                 return filename + " " + i;
453         }
454
455         /**
456          * Rename the conflicting trashed file with the supplied new name.
457          */
458         private void updateTrashedFile(String newName, FileResource trashedFile) {
459                 JSONObject json = new JSONObject();
460                 json.put("name", new JSONString(newName));
461                 PostCommand cf = new PostCommand(trashedFile.getUri() + "?update=", json.toString(), 200) {
462
463                         @Override
464                         public void onComplete() {
465                                 form.submit();
466                         }
467
468                         @Override
469                         public void onError(Throwable t) {
470                                 GSS app = GSS.get();
471                                 GWT.log("", t);
472                                 if (t instanceof RestException) {
473                                         int statusCode = ((RestException) t).getHttpStatusCode();
474                                         if (statusCode == 405)
475                                                 app.displayError("You don't have the necessary permissions");
476                                         else if (statusCode == 404)
477                                                 app.displayError("User in permissions does not exist");
478                                         else if (statusCode == 409)
479                                                 app.displayError("A file with the same name already exists");
480                                         else if (statusCode == 413)
481                                                 app.displayError("Your quota has been exceeded");
482                                         else
483                                                 app.displayError("Unable to modify file:" + ((RestException) t).getHttpStatusText());
484                                 } else
485                                         app.displayError("System error modifying file:" + t.getMessage());
486                         }
487
488                 };
489                 DeferredCommand.addCommand(cf);
490         }
491
492         protected FileResource getFileForName(String name){
493                 for (FileResource f : folder.getFiles())
494                         if (!f.isDeleted() && f.getName().equals(name))
495                                 return f;
496                 return null;
497         }
498
499
500         /**
501          * Same as URL.encodeComponent, but also
502          * encode apostrophe since browsers aren't consistent about it
503          * (FF encodes, IE does not).
504          */
505         private String encodeComponent(String decodedURLComponent) {
506                 String retv = URL.encodeComponent(decodedURLComponent);
507                 retv = retv.replaceAll("'", "%27");
508                 return retv;
509         }
510
511         /**
512          * Modify the files.
513          *
514          * @param newFiles the files to set
515          */
516         public void setFiles(List<FileResource> newFiles) {
517                 files = newFiles;
518         }
519 }