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