Finally displayed virtual folder hierarchy up to second level
[pithos] / web_client / src / gr / grnet / pithos / web / client / FileUploadGearsDialog.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.PostCommand;
7 import gr.grnet.pithos.web.client.rest.RestCommand;
8 import gr.grnet.pithos.web.client.rest.RestException;
9 import gr.grnet.pithos.web.client.rest.resource.FileResource;
10 import gr.grnet.pithos.web.client.rest.resource.RestResourceWrapper;
11
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17
18 import com.google.gwt.core.client.GWT;
19 import com.google.gwt.event.dom.client.ClickEvent;
20 import com.google.gwt.event.dom.client.ClickHandler;
21 import com.google.gwt.gears.client.Factory;
22 import com.google.gwt.gears.client.desktop.Desktop;
23 import com.google.gwt.gears.client.desktop.File;
24 import com.google.gwt.gears.client.desktop.OpenFilesHandler;
25 import com.google.gwt.gears.client.httprequest.HttpRequest;
26 import com.google.gwt.gears.client.httprequest.ProgressEvent;
27 import com.google.gwt.gears.client.httprequest.ProgressHandler;
28 import com.google.gwt.gears.client.httprequest.RequestCallback;
29 import com.google.gwt.http.client.URL;
30 import com.google.gwt.json.client.JSONObject;
31 import com.google.gwt.json.client.JSONString;
32 import com.google.gwt.user.client.DeferredCommand;
33 import com.google.gwt.user.client.ui.Button;
34 import com.google.gwt.user.client.ui.FlexTable;
35 import com.google.gwt.user.client.ui.HTML;
36 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
37 import com.google.gwt.user.client.ui.HorizontalPanel;
38 import com.google.gwt.user.client.ui.VerticalPanel;
39
40 /**
41  * The 'File upload' dialog box implementation with Google Gears support.
42  */
43 public class FileUploadGearsDialog extends FileUploadDialog implements Updateable {
44
45         protected final Factory factory = Factory.getInstance();
46
47         /**
48          * The array of files to upload.
49          */
50         private File[] fileObjects;
51
52         /**
53          * A list of files to upload, created from files array. Used to signal
54          * finished state when empty.
55          */
56         protected List<File> selectedFiles = new ArrayList<File>();
57
58         /**
59          * The list of progress bars for individual files.
60          */
61         protected List<ProgressBar> progressBars = new ArrayList<ProgressBar>();
62
63         private Button browse;
64
65         private Button submit;
66
67         private FlexTable generalTable;
68
69         private Map<String, FileResource> toRename;
70
71         protected List<HttpRequest> requests = new ArrayList<HttpRequest>();
72         
73         private boolean canContinue = true;
74
75         /**
76          * The widget's constructor.
77          */
78         public FileUploadGearsDialog() {
79                 // Set the dialog's caption.
80                 setText("File upload");
81                 setAnimationEnabled(true);
82                 // Create a panel to hold all of the dialog widgets.
83                 VerticalPanel panel = new VerticalPanel();
84                 final HTML info = new HTML("You may select one or more files to upload.");
85                 info.addStyleName("pithos-uploadNote");
86                 panel.add(info);
87                 // Add an informative label with the folder name.
88                 Object selection = GSS.get().getTreeView().getSelection();
89                 folder = ((RestResourceWrapper) selection).getResource();
90
91                 browse = new Button("Browse...");
92
93                 HorizontalPanel fileUploadPanel = new HorizontalPanel();
94                 fileUploadPanel.add(browse);
95
96                 generalTable = new FlexTable();
97                 generalTable.setText(0, 0, "Folder");
98                 generalTable.setText(1, 0, "File");
99                 generalTable.setText(0, 1, folder.getName());
100                 generalTable.setWidget(1, 1, fileUploadPanel);
101                 generalTable.getCellFormatter().setStyleName(0, 0, "props-labels");
102                 generalTable.getCellFormatter().setStyleName(1, 0, "props-labels");
103                 generalTable.getCellFormatter().setStyleName(0, 1, "props-values");
104                 generalTable.getCellFormatter().setStyleName(1, 1, "props-values");
105                 generalTable.setCellSpacing(4);
106
107                 panel.add(generalTable);
108
109                 // Create a panel to hold the buttons.
110                 HorizontalPanel buttons = new HorizontalPanel();
111
112                 submit = new Button("Upload");
113                 submit.addClickHandler(new ClickHandler() {
114                         @Override
115                         public void onClick(ClickEvent event) {
116                                 prepareAndSubmit();
117                         }
118                 });
119                 submit.setEnabled(false);
120                 buttons.add(submit);
121                 buttons.setCellHorizontalAlignment(submit, HasHorizontalAlignment.ALIGN_CENTER);
122                 // Create the 'Cancel' button, along with a listener that hides the
123                 // dialog when the button is clicked.
124                 Button cancel = new Button("Cancel", new ClickHandler() {
125                         @Override
126                         public void onClick(ClickEvent event) {
127                                 canContinue = false;                            
128                                 cancelUpload();                         
129                                 GSS.get().showFileList(true);
130                         }
131                 });
132                 buttons.add(cancel);
133                 buttons.setCellHorizontalAlignment(cancel, HasHorizontalAlignment.ALIGN_CENTER);
134                 buttons.setSpacing(8);
135                 buttons.addStyleName("pithos-DialogBox");
136
137                 browse.addClickHandler(new ClickHandler() {
138                         @Override
139                         public void onClick(ClickEvent event) {
140                                 Desktop desktop = factory.createDesktop();
141                                 desktop.openFiles(new OpenFilesHandler() {
142
143                                         @Override
144                                         public void onOpenFiles(OpenFilesEvent ofevent) {
145                                                 fileObjects = ofevent.getFiles();
146                                                 selectedFiles.addAll(Arrays.asList(fileObjects));
147                                                 for (int i = 0; i< selectedFiles.size(); i++) {
148                                                         generalTable.setText(i+1, 0, "File");
149                                                         generalTable.setText(i+1, 1, selectedFiles.get(i).getName());
150                                                         ProgressBar progress = new ProgressBar(20, 0);
151                                                         generalTable.setWidget(i+1, 2, progress);
152                                                         progressBars.add(progress);
153                                                         generalTable.getCellFormatter().setStyleName(i+1, 0, "props-labels");
154                                                         generalTable.getCellFormatter().setStyleName(i+1, 1, "props-values");
155                                                 }
156                                                 submit.setEnabled(true);
157                                         }
158                                 });
159                         }
160                 });
161
162                 panel.add(buttons);
163                 panel.setCellHorizontalAlignment(buttons, HasHorizontalAlignment.ALIGN_CENTER);
164                 panel.addStyleName("pithos-DialogBox");
165                 addStyleName("pithos-DialogBox");
166                 setWidget(panel);
167         }
168
169         /**
170          * Cancels the file upload.
171          */
172         private void cancelUpload() {
173                 for (HttpRequest request: requests)
174                         request.abort();
175                 hide();         
176         }
177
178         /**
179          * Check whether the specified file name exists in the selected folder.
180          */
181         private boolean canContinue(File file) {
182                 String fileName = getFilename(file.getName());
183                 if (getFileForName(fileName) == null)
184                         // For file creation, check to see if the file already exists.
185                         for (FileResource fileRes : files)
186                                 if (!fileRes.isDeleted() && fileRes.getName().equals(fileName))
187                                         return false;
188                 return true;
189         }
190
191         @Override
192         public void prepareAndSubmit() {
193                 GSS app = GSS.get();
194                 if (selectedFiles.size() == 0) {
195                         app.displayError("You must select a file!");
196                         hide();
197                         return;
198                 }
199                 for (File file: selectedFiles)
200                         if (!canContinue(file)) {
201                                 app.displayError("The file name " + file.getName() +
202                                                         " already exists in this folder");
203                                 hide();
204                                 return;
205                         }
206                 submit.setEnabled(false);
207                 browse.setVisible(false);
208                 List<String> toUpdate = new ArrayList<String>();
209                 toRename = new HashMap<String, FileResource>();
210                 for (File file: selectedFiles) {
211                         String fname = getFilename(file.getName());
212                         if (getFileForName(fname) == null) {
213                                 // We are going to create a file, so we check to see if there is a
214                                 // trashed file with the same name.
215                                 FileResource same = null;
216                                 for (FileResource fres : folder.getFiles())
217                                         if (fres.isDeleted() && fres.getName().equals(fname))
218                                                 same = fres;
219                                 // In that case add it to the list of files to rename.
220                                 if (same != null)
221                                         toRename.put(getBackupFilename(fname), same);
222                         } else
223                                 // If we are updating a file add it to the list of files to update.
224                                 toUpdate.add(fname);
225                 }
226
227                 if (!toUpdate.isEmpty()) {
228                         StringBuffer sb = new StringBuffer();
229                         for (String name: toUpdate)
230                                 sb.append(name).append("<br/>");
231                         // We are going to update existing files, so show a confirmation dialog.
232                         ConfirmationDialog confirm = new ConfirmationDialog("Are you sure " +
233                                         "you want to update the following files?<br/><i>" + sb +
234                                         "</i>", "Update") {
235
236                                 @Override
237                                 public void cancel() {
238                                         hide();
239                                 }
240
241                                 @Override
242                                 public void confirm() {
243                                         confirmRename();
244                                 }
245
246                         };
247                         confirm.center();
248                 } else
249                         confirmRename();
250         }
251
252         /**
253          * Confirm the renames of synonymous files already in the trash.
254          */
255         private void confirmRename() {
256                 if (!toRename.isEmpty()) {
257                         StringBuffer sb = new StringBuffer();
258                         for (FileResource file: toRename.values())
259                                 sb.append(file.getName()).append("<br/>");
260                         ConfirmationDialog confirm = new ConfirmationDialog("Files " +
261                                         "with the following names already exist in the trash. If" +
262                                         " you continue,<br/>the trashed files will be renamed " +
263                                         "automatically for you:<br/><i>" + sb + "</i>", "Continue") {
264
265                                 @Override
266                                 public void cancel() {
267                                         hide();
268                                 }
269
270                                 @Override
271                                 public void confirm() {
272                                         updateTrashedFiles();
273                                 }
274
275                         };
276                         confirm.center();
277                 } else
278                         uploadFiles();
279         }
280
281         /**
282          * Rename the conflicting trashed files with the supplied new names.
283          */
284         private void updateTrashedFiles() {
285                 for (final String name: toRename.keySet()) {
286                         JSONObject json = new JSONObject();
287                         json.put("name", new JSONString(name));
288                         PostCommand cf = new PostCommand(toRename.get(name).getUri() + "?update=", json.toString(), 200) {
289
290                                 @Override
291                                 public void onComplete() {
292                                         toRename.remove(name);
293                                         uploadFiles();
294                                 }
295
296                                 @Override
297                                 public void onError(Throwable t) {
298                                         GSS app = GSS.get();
299                                         GWT.log("", t);
300                                         if (t instanceof RestException) {
301                                                 int statusCode = ((RestException) t).getHttpStatusCode();
302                                                 if (statusCode == 405)
303                                                         app.displayError("You don't have the necessary permissions");
304                                                 else if (statusCode == 404)
305                                                         app.displayError("User in permissions does not exist");
306                                                 else if (statusCode == 409)
307                                                         app.displayError("A file with the same name already exists");
308                                                 else if (statusCode == 413)
309                                                         app.displayError("Your quota has been exceeded");
310                                                 else
311                                                         app.displayError("Unable to modify file:" + ((RestException) t).getHttpStatusText());
312                                         } else
313                                                 app.displayError("System error modifying file:" + t.getMessage());
314                                 }
315
316                         };
317                         DeferredCommand.addCommand(cf);
318                 }
319         }
320
321         /**
322          * Checks if the renaming step for already trashed files is complete and
323          * starts file uploads.
324          */
325         private void uploadFiles() {            
326                 if (!toRename.isEmpty()) return;
327                 if (canContinue){                                               
328                         doSend(selectedFiles);
329                 }
330         }
331
332         /**
333          * Perform the HTTP request to upload the specified file.
334          */
335         protected void doSend(final List<File> filesRemaining) {
336                 final GSS app = GSS.get();
337                 HttpRequest request = factory.createHttpRequest();
338                 requests.add(request);
339                 String method = "PUT";
340
341                 String path;
342                 final String filename = getFilename(filesRemaining.get(0).getName());
343                 path = folder.getUri();
344                 if (!path.endsWith("/"))
345                         path = path + "/";
346                 path = path + encode(filename);
347
348                 String token = app.getToken();
349                 String resource = path.substring(app.getApiPath().length()-1, path.length());
350                 String date = RestCommand.getDate();
351                 String sig = RestCommand.calculateSig(method, date, resource, RestCommand.base64decode(token));
352                 request.open(method, path);
353                 request.setRequestHeader("X-GSS-Date", date);
354                 request.setRequestHeader("Authorization", app.getCurrentUserResource().getUsername() + " " + sig);
355                 request.setRequestHeader("Accept", "application/json; charset=utf-8");
356                 request.setCallback(new RequestCallback() {
357                         @Override
358                         public void onResponseReceived(HttpRequest req) {
359                                 int state = req.getReadyState();
360                                 if (state != 4) return;
361                                 switch(req.getStatus()) {
362                                         case 201: // Created falls through to updated.
363                                         case 204:
364                                                 filesRemaining.remove(0);
365                                                 if(filesRemaining.isEmpty()){                                                   
366                                                         finish();
367                                                         break;
368                                                 }                                               
369                                                 doSend(filesRemaining);                         
370                                                 break;
371                                         case 403:
372                                                 SessionExpiredDialog dlg = new SessionExpiredDialog();
373                                                 dlg.center();
374                                                 break;
375                                         case 405:
376                                                 app.displayError("You don't have permission to " +
377                                                                 "upload file " + filename);
378                                                 break;
379                                         case 409:
380                                                 app.displayError("A folder with the name " + filename +
381                                                                 " already exists at this level");
382                                                 break;
383                                         case 413:
384                                                 app.displayError("There is not enough free space " +
385                                                                 "available for uploading " + filename);
386                                                 break;
387                                         default:
388                                                 app.displayError("Error uploading file " + filename +
389                                                                         ": " + req.getStatus());
390                                 }
391                         }
392                 });
393                 request.getUpload().setProgressHandler(new ProgressHandler() {
394                         @Override
395                         public void onProgress(ProgressEvent event) {
396                                 double pcnt = (double) event.getLoaded() / event.getTotal();
397                                 progressBars.get(0).setProgress((int) Math.floor(pcnt * 100));
398                                 if(pcnt*100 == 100)
399                                         progressBars.remove(0);
400                         }
401                 });
402                 request.send(filesRemaining.get(0).getBlob());
403         }
404
405         /**
406          * Perform the final actions after the files are uploaded.
407          */
408         protected void finish() {
409                 hide();
410                 //GSS.get().showFileList(true);
411                 GSS.get().getTreeView().updateNode(GSS.get().getTreeView().getSelection());//showFileList(true);
412                 GSS.get().getStatusPanel().updateStats();
413         }
414
415         /**
416          * Same as URL.encode, but also encode apostrophe since browsers aren't
417          * consistent about it (FF encodes, IE does not).
418          */
419         protected String encode(String decodedURL) {
420                 String retv = decodedURL.replaceAll("@", "_"); // Replace bad character
421                 retv = URL.encodeComponent(retv);
422                 retv = retv.replaceAll("'", "%27");
423                 return retv;
424         }
425
426 }