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