Fixed a race condition duw to which renamed shared folders continued showing in mysha...
[pithos-web-client] / src / gr / grnet / pithos / web / client / Pithos.java
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35 package gr.grnet.pithos.web.client;
36
37 import gr.grnet.pithos.web.client.commands.UploadFileCommand;
38 import gr.grnet.pithos.web.client.foldertree.AccountResource;
39 import gr.grnet.pithos.web.client.foldertree.File;
40 import gr.grnet.pithos.web.client.foldertree.Folder;
41 import gr.grnet.pithos.web.client.foldertree.FolderTreeView;
42 import gr.grnet.pithos.web.client.foldertree.FolderTreeViewModel;
43 import gr.grnet.pithos.web.client.foldertree.Resource;
44 import gr.grnet.pithos.web.client.grouptree.Group;
45 import gr.grnet.pithos.web.client.grouptree.GroupTreeView;
46 import gr.grnet.pithos.web.client.grouptree.GroupTreeViewModel;
47 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeView;
48 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeViewModel;
49 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeView;
50 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeViewModel;
51 import gr.grnet.pithos.web.client.rest.DeleteRequest;
52 import gr.grnet.pithos.web.client.rest.GetRequest;
53 import gr.grnet.pithos.web.client.rest.HeadRequest;
54 import gr.grnet.pithos.web.client.rest.PutRequest;
55 import gr.grnet.pithos.web.client.rest.RestException;
56
57 import java.util.ArrayList;
58 import java.util.HashMap;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Set;
62
63 import com.google.gwt.core.client.EntryPoint;
64 import com.google.gwt.core.client.GWT;
65 import com.google.gwt.core.client.Scheduler;
66 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
67 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
68 import com.google.gwt.event.dom.client.ClickEvent;
69 import com.google.gwt.event.dom.client.ClickHandler;
70 import com.google.gwt.event.logical.shared.ResizeEvent;
71 import com.google.gwt.event.logical.shared.ResizeHandler;
72 import com.google.gwt.http.client.Request;
73 import com.google.gwt.http.client.RequestBuilder;
74 import com.google.gwt.http.client.RequestCallback;
75 import com.google.gwt.http.client.RequestException;
76 import com.google.gwt.http.client.Response;
77 import com.google.gwt.http.client.URL;
78 import com.google.gwt.i18n.client.Dictionary;
79 import com.google.gwt.i18n.client.NumberFormat;
80 import com.google.gwt.json.client.JSONArray;
81 import com.google.gwt.json.client.JSONObject;
82 import com.google.gwt.json.client.JSONParser;
83 import com.google.gwt.json.client.JSONString;
84 import com.google.gwt.json.client.JSONValue;
85 import com.google.gwt.resources.client.ImageResource;
86 import com.google.gwt.resources.client.ImageResource.ImageOptions;
87 import com.google.gwt.user.client.Command;
88 import com.google.gwt.user.client.Cookies;
89 import com.google.gwt.user.client.Event;
90 import com.google.gwt.user.client.History;
91 import com.google.gwt.user.client.Window;
92 import com.google.gwt.user.client.ui.AbstractImagePrototype;
93 import com.google.gwt.user.client.ui.Button;
94 import com.google.gwt.user.client.ui.Composite;
95 import com.google.gwt.user.client.ui.HTML;
96 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
97 import com.google.gwt.user.client.ui.HasVerticalAlignment;
98 import com.google.gwt.user.client.ui.HorizontalPanel;
99 import com.google.gwt.user.client.ui.HorizontalSplitPanel;
100 import com.google.gwt.user.client.ui.RootPanel;
101 import com.google.gwt.user.client.ui.VerticalPanel;
102 import com.google.gwt.view.client.SelectionChangeEvent;
103 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
104 import com.google.gwt.view.client.SingleSelectionModel;
105
106 /**
107  * Entry point classes define <code>onModuleLoad()</code>.
108  */
109 public class Pithos implements EntryPoint, ResizeHandler {
110
111         public static final String HOME_CONTAINER = "pithos";
112
113         public static final String TRASH_CONTAINER = "trash";
114
115         public static final Configuration config = GWT.create(Configuration.class);
116         
117         /**
118          * Instantiate an application-level image bundle. This object will provide
119          * programmatic access to all the images needed by widgets.
120          */
121         static Images images = (Images) GWT.create(Images.class);
122
123     public String getUsername() {
124         return username;
125     }
126
127     public void setAccount(AccountResource acct) {
128         account = acct;
129     }
130
131     public AccountResource getAccount() {
132         return account;
133     }
134
135     public void updateFolder(Folder f, boolean showfiles, Command callback) {
136         folderTreeView.updateFolder(f, showfiles, callback);
137     }
138
139     public void updateGroupNode(Group group) {
140         groupTreeView.updateGroupNode(group);
141     }
142
143     public void updateMySharedRoot() {
144         mysharedTreeView.updateRoot();
145     }
146     
147     public void updateSharedFolder(Folder f, boolean showfiles) {
148         mysharedTreeView.updateFolder(f, showfiles);
149     }
150     
151     public void updateOtherSharedFolder(Folder f, boolean showfiles) {
152         otherSharedTreeView.updateFolder(f, showfiles);
153     }
154
155     public MysharedTreeView getMySharedTreeView() {
156         return mysharedTreeView;
157     }
158
159     /**
160          * An aggregate image bundle that pulls together all the images for this
161          * application into a single bundle.
162          */
163         public interface Images extends TopPanel.Images, FileList.Images, ToolsMenu.Images {
164
165                 @Source("gr/grnet/pithos/resources/document.png")
166                 ImageResource folders();
167
168                 @Source("gr/grnet/pithos/resources/advancedsettings.png")
169                 @ImageOptions(width=32, height=32)
170                 ImageResource tools();
171         }
172
173         private Throwable error;
174         
175         /**
176          * The Application Clipboard implementation;
177          */
178         private Clipboard clipboard = new Clipboard();
179
180         /**
181          * The top panel that contains the menu bar.
182          */
183         private TopPanel topPanel;
184
185         /**
186          * The panel that contains the various system messages.
187          */
188         private MessagePanel messagePanel = new MessagePanel(this, Pithos.images);
189
190         /**
191          * The bottom panel that contains the status bar.
192          */
193         private StatusPanel statusPanel = null;
194
195         /**
196          * The file list widget.
197          */
198         private FileList fileList;
199
200         /**
201          * The tab panel that occupies the right side of the screen.
202          */
203         private VerticalPanel inner = new VerticalPanel();
204
205
206         /**
207          * The split panel that will contain the left and right panels.
208          */
209         private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
210
211         /**
212          * The currently selected item in the application, for use by the Edit menu
213          * commands. Potential types are Folder, File, User and Group.
214          */
215         private Object currentSelection;
216
217         public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
218
219     private String username = null;
220
221     /**
222      * The authentication token of the current user.
223      */
224     private String token;
225
226     VerticalPanel trees;
227     
228     SingleSelectionModel<Folder> folderTreeSelectionModel;
229     FolderTreeViewModel folderTreeViewModel;
230     FolderTreeView folderTreeView;
231
232     SingleSelectionModel<Folder> mysharedTreeSelectionModel;
233     MysharedTreeViewModel mysharedTreeViewModel;
234     MysharedTreeView mysharedTreeView = null;;
235
236     protected SingleSelectionModel<Folder> otherSharedTreeSelectionModel;
237     OtherSharedTreeViewModel otherSharedTreeViewModel;
238     OtherSharedTreeView otherSharedTreeView = null;
239
240     GroupTreeViewModel groupTreeViewModel;
241     private GroupTreeView groupTreeView;
242
243     TreeView selectedTree;
244     protected AccountResource account;
245     
246     Folder trash;
247     
248     List<Composite> treeViews = new ArrayList<Composite>();
249
250     @SuppressWarnings("rawtypes") List<SingleSelectionModel> selectionModels = new ArrayList<SingleSelectionModel>();
251     
252     Button upload;
253     
254     private HTML usedBytes;
255     
256     private HTML totalBytes;
257     
258     private HTML usedPercent;
259     
260     private HTML numOfFiles;
261     
262     private Toolbar toolbar;
263     
264     private FileUploadDialog fileUploadDialog;
265     
266         @Override
267         public void onModuleLoad() {
268                 if (parseUserCredentials())
269             initialize();
270         }
271
272     private void initialize() {
273         boolean bareContent = Window.Location.getParameter("noframe") != null;
274         String contentWidth = bareContent ? "100%" : "75%";
275
276         VerticalPanel outer = new VerticalPanel();
277         outer.setWidth("100%");
278         if (!bareContent) {
279                 outer.addStyleName("pithos-outer");
280         }
281
282         if (!bareContent) {
283                 topPanel = new TopPanel(this, Pithos.images);
284                 topPanel.setWidth("100%");
285                 outer.add(topPanel);
286                 outer.setCellHorizontalAlignment(topPanel, HasHorizontalAlignment.ALIGN_CENTER);
287         }
288         
289         messagePanel.setVisible(false);
290         outer.add(messagePanel);
291         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
292         outer.setCellVerticalAlignment(messagePanel, HasVerticalAlignment.ALIGN_MIDDLE);
293
294         HorizontalPanel header = new HorizontalPanel();
295         header.addStyleName("pithos-header");
296         header.setWidth(contentWidth);
297         if (bareContent)
298                 header.addStyleName("pithos-header-noframe");
299         upload = new Button("Upload", new ClickHandler() {
300             @Override
301             public void onClick(ClickEvent event) {
302                 if (getSelection() != null)
303                         new UploadFileCommand(Pithos.this, null, getSelection()).execute();
304             }
305         });
306         upload.addStyleName("pithos-uploadButton");
307         header.add(upload);
308         header.setCellHorizontalAlignment(upload, HasHorizontalAlignment.ALIGN_LEFT);
309         header.setCellVerticalAlignment(upload, HasVerticalAlignment.ALIGN_MIDDLE);
310
311         toolbar = new Toolbar(this);
312         header.add(toolbar);
313         header.setCellHorizontalAlignment(toolbar, HasHorizontalAlignment.ALIGN_CENTER);
314         header.setCellVerticalAlignment(toolbar, HasVerticalAlignment.ALIGN_MIDDLE);
315         
316         HorizontalPanel folderStatistics = new HorizontalPanel();
317         folderStatistics.addStyleName("pithos-folderStatistics");
318         numOfFiles = new HTML();
319         folderStatistics.add(numOfFiles);
320         folderStatistics.setCellVerticalAlignment(numOfFiles, HasVerticalAlignment.ALIGN_MIDDLE);
321         HTML numOfFilesLabel = new HTML("&nbsp;Files");
322         folderStatistics.add(numOfFilesLabel);
323         folderStatistics.setCellVerticalAlignment(numOfFilesLabel, HasVerticalAlignment.ALIGN_MIDDLE);
324         header.add(folderStatistics);
325         header.setCellHorizontalAlignment(folderStatistics, HasHorizontalAlignment.ALIGN_RIGHT);
326         header.setCellVerticalAlignment(folderStatistics, HasVerticalAlignment.ALIGN_MIDDLE);
327         header.setCellWidth(folderStatistics, "40px");
328         outer.add(header);
329         outer.setCellHorizontalAlignment(header, HasHorizontalAlignment.ALIGN_CENTER);
330         // Inner contains the various lists.nner
331         inner.sinkEvents(Event.ONCONTEXTMENU);
332         inner.setWidth("100%");
333
334         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
335         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
336             @Override
337             public void onSelectionChange(SelectionChangeEvent event) {
338                 if (folderTreeSelectionModel.getSelectedObject() != null) {
339                     deselectOthers(folderTreeView, folderTreeSelectionModel);
340                     applyPermissions(folderTreeSelectionModel.getSelectedObject());
341                     Folder f = folderTreeSelectionModel.getSelectedObject();
342                         updateFolder(f, true, new Command() {
343                                 
344                                 @Override
345                                 public void execute() {
346                                         updateStatistics();
347                                 }
348                         });
349                         showRelevantToolbarButtons();
350                 }
351                                 else {
352                                         if (getSelectedTree().equals(folderTreeView))
353                                                 setSelectedTree(null);
354                                         if (getSelectedTree() == null)
355                                                 showRelevantToolbarButtons();
356                                 }
357             }
358         });
359         selectionModels.add(folderTreeSelectionModel);
360
361         folderTreeViewModel = new FolderTreeViewModel(this, folderTreeSelectionModel);
362         folderTreeView = new FolderTreeView(folderTreeViewModel);
363         treeViews.add(folderTreeView);
364         
365         fileList = new FileList(this, images, folderTreeView);
366         inner.add(fileList);
367
368         groupTreeViewModel = new GroupTreeViewModel(this);
369         groupTreeView = new GroupTreeView(groupTreeViewModel);
370         treeViews.add(groupTreeView);
371         
372         trees = new VerticalPanel();
373         trees.setWidth("100%");
374
375         
376         HorizontalPanel treeHeader = new HorizontalPanel();
377         treeHeader.addStyleName("pithos-treeHeader");
378         treeHeader.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
379         treeHeader.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
380         HorizontalPanel statistics = new HorizontalPanel();
381         statistics.addStyleName("pithos-statistics");
382         statistics.add(new HTML("Used:&nbsp;"));
383         usedBytes = new HTML();
384         statistics.add(usedBytes);
385         statistics.add(new HTML("&nbsp;of&nbsp;"));
386         totalBytes = new HTML();
387         statistics.add(totalBytes);
388         statistics.add(new HTML("&nbsp;("));
389         usedPercent = new HTML();
390         statistics.add(usedPercent);
391         statistics.add(new HTML(")"));
392         treeHeader.add(statistics);
393         treeHeader.setCellHorizontalAlignment(statistics, HasHorizontalAlignment.ALIGN_LEFT);
394         trees.add(treeHeader);
395
396         trees.add(folderTreeView);
397         trees.add(groupTreeView);
398         // Add the left and right panels to the split panel.
399         splitPanel.setLeftWidget(trees);
400         splitPanel.setRightWidget(inner);
401         splitPanel.setSplitPosition("35%");
402         splitPanel.setSize("100%", "100%");
403         splitPanel.addStyleName("pithos-splitPanel");
404         splitPanel.setWidth(contentWidth);
405         outer.add(splitPanel);
406         outer.setCellHorizontalAlignment(splitPanel, HasHorizontalAlignment.ALIGN_CENTER);
407
408         if (!bareContent) {
409                 statusPanel = new StatusPanel();
410                 statusPanel.setWidth("100%");
411                 outer.add(statusPanel);
412                 outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
413         }
414         else
415                 splitPanel.addStyleName("pithos-splitPanel-noframe");
416
417         // Hook the window resize event, so that we can adjust the UI.
418         Window.addResizeHandler(this);
419         // Clear out the window's built-in margin, because we want to take
420         // advantage of the entire client area.
421         Window.setMargin("0px");
422         // Finally, add the outer panel to the RootPanel, so that it will be
423         // displayed.
424         RootPanel.get().add(outer);
425         // Call the window resized handler to get the initial sizes setup. Doing
426         // this in a deferred command causes it to occur after all widgets'
427         // sizes have been computed by the browser.
428         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
429
430             @Override
431             public void execute() {
432                 onWindowResized(Window.getClientHeight());
433             }
434         });
435
436         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
437             @Override
438             public void execute() {
439                 fetchAccount(new Command() {
440                                         
441                                         @Override
442                                         public void execute() {
443                                 if (!account.hasHomeContainer())
444                                     createHomeContainer(account, this);
445                                 else if (!account.hasTrashContainer())
446                                         createTrashContainer(this);
447                                 else {
448                                         for (Folder f : account.getContainers())
449                                                 if (f.getName().equals(Pithos.TRASH_CONTAINER)) {
450                                                         trash = f;
451                                                         break;
452                                                 }
453                                     folderTreeViewModel.initialize(account, new Command() {
454                                                                 
455                                                                 @Override
456                                                                 public void execute() {
457                                                     createMySharedTree();
458                                                                 }
459                                                         });
460                                     groupTreeViewModel.initialize();
461                                     showStatistics();
462                                 }
463                                         }
464                                 });
465             }
466         });
467         
468 //        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
469 //                      
470 //                      @Override
471 //                      public boolean execute() {
472 //                              Folder f = getSelection();
473 //                              if (f != null) {
474 //                                      if (getSelectedTree().equals(folderTreeView))
475 //                                              updateFolder(f, true, null);
476 //                                      else if (getSelectedTree().equals(mysharedTreeView))
477 //                                              updateSharedFolder(f, true);
478 //                              }
479 //                              return true;
480 //                      }
481 //              }, 3000);
482     }
483
484     public void applyPermissions(Folder f) {
485         if (f != null) {
486                 if (f.isInTrash())
487                         upload.setEnabled(false);
488                 else {
489                         Boolean[] perms = f.getPermissions().get(username);
490                         if (f.getOwner().equals(username) || (perms != null && perms[1] != null && perms[1])) {
491                                 upload.setEnabled(true);
492                         }
493                         else
494                                 upload.setEnabled(false);
495                 }
496         }
497         else
498                 upload.setEnabled(false);
499         }
500
501         @SuppressWarnings({ "rawtypes", "unchecked" })
502         public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
503         selectedTree = _selectedTree;
504         
505         for (Composite c : treeViews)
506                 if (c.equals(selectedTree))
507                         c.addStyleName("cellTreeWidget-selectedTree");
508                 else
509                         c.removeStyleName("cellTreeWidget-selectedTree");
510         
511         for (SingleSelectionModel s : selectionModels)
512             if (!s.equals(model) && s.getSelectedObject() != null)
513                 s.setSelected(s.getSelectedObject(), false);
514     }
515
516     public void showFiles(final Folder f) {
517         Set<File> files = f.getFiles();
518         showFiles(files);
519     }
520
521     public void showFiles(Set<File> files) {
522         fileList.setFiles(new ArrayList<File>(files));
523     }
524
525     /**
526          * Parse and store the user credentials to the appropriate fields.
527          */
528         private boolean parseUserCredentials() {
529         username = Window.Location.getParameter("user");
530         token = Window.Location.getParameter("token");
531         Configuration conf = (Configuration) GWT.create(Configuration.class);
532                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
533         if (username == null || username.length() == 0 || token == null || token.length() == 0) {
534             String cookie = otherProperties.get("authCookie");
535             String auth = Cookies.getCookie(cookie);
536             if (auth == null) {
537                 authenticateUser();
538                 return false;
539             }
540             if (auth.startsWith("\""))
541                 auth = auth.substring(1);
542             if (auth.endsWith("\""))
543                 auth = auth.substring(0, auth.length() - 1);
544                         String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
545                         if (authSplit.length != 2) {
546                             authenticateUser();
547                             return false;
548                         }
549                         username = authSplit[0];
550                         token = authSplit[1];
551         }
552         else
553                 Cookies.setCookie(otherProperties.get("authCookie"), username + conf.cookieSeparator() + token, null, "", "/", false);
554
555         String gotoUrl = Window.Location.getParameter("goto");
556                 if (gotoUrl != null && gotoUrl.length() > 0) {
557                         Window.Location.assign(gotoUrl);
558                         return false;
559                 }
560                 return true;
561     }
562
563     /**
564          * Redirect the user to the login page for authentication.
565          */
566         protected void authenticateUser() {
567                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
568         Window.Location.assign(otherProperties.get("loginUrl") + Window.Location.getHref());
569         }
570
571         protected void fetchAccount(final Command callback) {
572         String path = "?format=json";
573
574         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), username, path) {
575             @Override
576             public void onSuccess(AccountResource _result) {
577                 account = _result;
578                 if (callback != null)
579                         callback.execute();
580             }
581
582             @Override
583             public void onError(Throwable t) {
584                 GWT.log("Error getting account", t);
585                                 setError(t);
586                 if (t instanceof RestException)
587                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
588                 else
589                     displayError("System error fetching user data: " + t.getMessage());
590             }
591
592                         @Override
593                         protected void onUnauthorized(Response response) {
594                                 sessionExpired();
595                         }
596         };
597         getAccount.setHeader("X-Auth-Token", token);
598         Scheduler.get().scheduleDeferred(getAccount);
599     }
600
601     public void updateStatistics() {
602         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), username, "", account) {
603
604                         @Override
605                         public void onSuccess(AccountResource _result) {
606                                 showStatistics();
607                         }
608
609                         @Override
610                         public void onError(Throwable t) {
611                 GWT.log("Error getting account", t);
612                                 setError(t);
613                 if (t instanceof RestException)
614                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
615                 else
616                     displayError("System error fetching user data: " + t.getMessage());
617                         }
618
619                         @Override
620                         protected void onUnauthorized(Response response) {
621                                 sessionExpired();
622                         }
623                 };
624                 headAccount.setHeader("X-Auth-Token", token);
625                 Scheduler.get().scheduleDeferred(headAccount);
626         }
627
628         protected void showStatistics() {
629         usedBytes.setHTML(String.valueOf(account.getFileSizeAsString()));
630         totalBytes.setHTML(String.valueOf(account.getQuotaAsString()));
631         NumberFormat nf = NumberFormat.getPercentFormat();
632         usedPercent.setHTML(nf.format(account.getUsedPercentage()));
633         }
634
635         protected void createHomeContainer(final AccountResource _account, final Command callback) {
636         String path = "/" + Pithos.HOME_CONTAINER;
637         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
638             @Override
639             public void onSuccess(Resource result) {
640                 if (!_account.hasTrashContainer())
641                         createTrashContainer(callback);
642                 else
643                         fetchAccount(callback);
644             }
645
646             @Override
647             public void onError(Throwable t) {
648                 GWT.log("Error creating pithos", t);
649                                 setError(t);
650                 if (t instanceof RestException)
651                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
652                 else
653                     displayError("System error Error creating pithos: " + t.getMessage());
654             }
655
656                         @Override
657                         protected void onUnauthorized(Response response) {
658                                 sessionExpired();
659                         }
660         };
661         createPithos.setHeader("X-Auth-Token", getToken());
662         Scheduler.get().scheduleDeferred(createPithos);
663     }
664
665     protected void createTrashContainer(final Command callback) {
666         String path = "/" + Pithos.TRASH_CONTAINER;
667         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
668             @Override
669             public void onSuccess(Resource result) {
670                         fetchAccount(callback);
671             }
672
673             @Override
674             public void onError(Throwable t) {
675                 GWT.log("Error creating pithos", t);
676                                 setError(t);
677                 if (t instanceof RestException)
678                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
679                 else
680                     displayError("System error Error creating pithos: " + t.getMessage());
681             }
682
683                         @Override
684                         protected void onUnauthorized(Response response) {
685                                 sessionExpired();
686                         }
687         };
688         createPithos.setHeader("X-Auth-Token", getToken());
689         Scheduler.get().scheduleDeferred(createPithos);
690     }
691
692     /**
693          * Creates an HTML fragment that places an image & caption together, for use
694          * in a group header.
695          *
696          * @param imageProto an image prototype for an image
697          * @param caption the group caption
698          * @return the header HTML fragment
699          */
700         private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
701                 String captionHTML = "<table class='caption' cellpadding='0' " 
702                 + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML() 
703                 + "</td><td id =" + caption +" class='rcaption'><b style='white-space:nowrap'>&nbsp;" 
704                 + caption + "</b></td></tr></table>";
705                 return captionHTML;
706         }
707
708         protected void onWindowResized(int height) {
709                 // Adjust the split panel to take up the available room in the window.
710                 int newHeight = height - splitPanel.getAbsoluteTop();
711                 if (newHeight < 1)
712                         newHeight = 1;
713                 splitPanel.setHeight("" + newHeight);
714                 inner.setHeight("" + newHeight);
715         }
716
717         @Override
718         public void onResize(ResizeEvent event) {
719                 int height = event.getHeight();
720                 onWindowResized(height);
721         }
722
723         /**
724          * Display an error message.
725          *
726          * @param msg the message to display
727          */
728         public void displayError(String msg) {
729                 messagePanel.displayError(msg);
730         }
731
732         /**
733          * Display a warning message.
734          *
735          * @param msg the message to display
736          */
737         public void displayWarning(String msg) {
738                 messagePanel.displayWarning(msg);
739         }
740
741         /**
742          * Display an informational message.
743          *
744          * @param msg the message to display
745          */
746         public void displayInformation(String msg) {
747                 messagePanel.displayInformation(msg);
748         }
749
750         /**
751          * Retrieve the fileList.
752          *
753          * @return the fileList
754          */
755         public FileList getFileList() {
756                 return fileList;
757         }
758
759         /**
760          * Retrieve the topPanel.
761          *
762          * @return the topPanel
763          */
764         TopPanel getTopPanel() {
765                 return topPanel;
766         }
767
768         /**
769          * Retrieve the clipboard.
770          *
771          * @return the clipboard
772          */
773         public Clipboard getClipboard() {
774                 return clipboard;
775         }
776
777         public StatusPanel getStatusPanel() {
778                 return statusPanel;
779         }
780
781         public String getToken() {
782                 return token;
783         }
784
785         public static native void preventIESelection() /*-{
786                 $doc.body.onselectstart = function () { return false; };
787         }-*/;
788
789         public static native void enableIESelection() /*-{
790                 if ($doc.body.onselectstart != null)
791                 $doc.body.onselectstart = null;
792         }-*/;
793
794         /**
795          * @return the absolute path of the API root URL
796          */
797         public String getApiPath() {
798                 Configuration conf = (Configuration) GWT.create(Configuration.class);
799                 return conf.apiPath();
800         }
801
802         /**
803          * History support for folder navigation
804          * adds a new browser history entry
805          *
806          * @param key
807          */
808         public void updateHistory(String key){
809 //              Replace any whitespace of the initial string to "+"
810 //              String result = key.replaceAll("\\s","+");
811 //              Add a new browser history entry.
812 //              History.newItem(result);
813                 History.newItem(key);
814         }
815
816     public void deleteFolder(final Folder folder, final Command callback) {
817         String path = getApiPath() + folder.getOwner() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(folder.getPrefix()) + "&t=" + System.currentTimeMillis();
818         RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
819         builder.setHeader("X-Auth-Token", getToken());
820         try {
821             builder.sendRequest("", new RequestCallback() {
822                 @Override
823                 public void onResponseReceived(Request request, Response response) {
824                     if (response.getStatusCode() == Response.SC_OK) {
825                         JSONValue json = JSONParser.parseStrict(response.getText());
826                         JSONArray array = json.isArray();
827                         int i = 0;
828                         if (array != null) {
829                             deleteObject(folder, i, array, callback);
830                         }
831                     }
832                 }
833
834                 @Override
835                 public void onError(Request request, Throwable exception) {
836                         setError(exception);
837                     displayError("System error unable to delete folder: " + exception.getMessage());
838                 }
839             });
840         }
841         catch (RequestException e) {
842         }
843     }
844
845     void deleteObject(final Folder folder, final int i, final JSONArray array, final Command callback) {
846         if (i < array.size()) {
847             JSONObject o = array.get(i).isObject();
848             if (o != null && !o.containsKey("subdir")) {
849                 JSONString name = o.get("name").isString();
850                 String path = "/" + folder.getContainer() + "/" + name.stringValue();
851                 DeleteRequest delete = new DeleteRequest(getApiPath(), folder.getOwner(), URL.encode(path)) {
852                     @Override
853                     public void onSuccess(Resource result) {
854                         deleteObject(folder, i + 1, array, callback);
855                     }
856
857                     @Override
858                     public void onError(Throwable t) {
859                         GWT.log("", t);
860                                                 setError(t);
861                         displayError("System error unable to delete folder: " + t.getMessage());
862                     }
863
864                                 @Override
865                                 protected void onUnauthorized(Response response) {
866                                         sessionExpired();
867                                 }
868                 };
869                 delete.setHeader("X-Auth-Token", getToken());
870                 Scheduler.get().scheduleDeferred(delete);
871             }
872             else if (o != null) {
873                 String subdir = o.get("subdir").isString().stringValue();
874                 subdir = subdir.substring(0, subdir.length() - 1);
875                 String path = getApiPath() + getUsername() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(subdir) + "&t=" + System.currentTimeMillis();
876                 RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
877                 builder.setHeader("X-Auth-Token", getToken());
878                 try {
879                     builder.sendRequest("", new RequestCallback() {
880                         @Override
881                         public void onResponseReceived(Request request, Response response) {
882                             if (response.getStatusCode() == Response.SC_OK) {
883                                 JSONValue json = JSONParser.parseStrict(response.getText());
884                                 JSONArray array2 = json.isArray();
885                                 if (array2 != null) {
886                                     int l = array.size();
887                                     for (int j=0; j<array2.size(); j++) {
888                                         array.set(l++, array2.get(j));
889                                     }
890                                 }
891                                 deleteObject(folder, i + 1, array, callback);
892                             }
893                         }
894
895                         @Override
896                         public void onError(Request request, Throwable exception) {
897                                 setError(exception);
898                             displayError("System error unable to delete folder: " + exception.getMessage());
899                         }
900                     });
901                 }
902                 catch (RequestException e) {
903                 }
904             }
905         }
906         else {
907             String path = folder.getUri();
908             DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), getUsername(), URL.encode(path)) {
909                 @Override
910                 public void onSuccess(Resource result) {
911                     updateFolder(folder.getParent(), true, new Command() {
912                                                 
913                                                 @Override
914                                                 public void execute() {
915                                                         updateStatistics();
916                                                         if (callback != null)
917                                                                 callback.execute();
918                                                 }
919                                         });
920                 }
921
922                 @Override
923                 public void onError(Throwable t) {
924                     GWT.log("", t);
925                                         setError(t);
926                     if (t instanceof RestException) {
927                         if (((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND)
928                                 displayError("Unable to delete folder: "+((RestException) t).getHttpStatusText());
929                         else
930                                 onSuccess(null);
931                     }
932                     else
933                         displayError("System error unable to delete folder: " + t.getMessage());
934                 }
935
936                                 @Override
937                                 protected void onUnauthorized(Response response) {
938                                         sessionExpired();
939                                 }
940             };
941             deleteFolder.setHeader("X-Auth-Token", getToken());
942             Scheduler.get().scheduleDeferred(deleteFolder);
943         }
944     }
945
946     public FolderTreeView getFolderTreeView() {
947         return folderTreeView;
948     }
949
950     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
951         if (iter.hasNext()) {
952             File file = iter.next();
953             String path = targetUri + "/" + file.getName();
954             PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
955                 @Override
956                 public void onSuccess(Resource result) {
957                     copyFiles(iter, targetUsername, targetUri, callback);
958                 }
959
960                 @Override
961                 public void onError(Throwable t) {
962                     GWT.log("", t);
963                                         setError(t);
964                     if (t instanceof RestException) {
965                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
966                     }
967                     else
968                         displayError("System error unable to copy file: "+t.getMessage());
969                 }
970
971                                 @Override
972                                 protected void onUnauthorized(Response response) {
973                                         sessionExpired();
974                                 }
975             };
976             copyFile.setHeader("X-Auth-Token", getToken());
977             copyFile.setHeader("X-Copy-From", URL.encodePathSegment(file.getUri()));
978             if (!file.getOwner().equals(targetUsername))
979                 copyFile.setHeader("X-Source-Account", URL.encodePathSegment(file.getOwner()));
980             copyFile.setHeader("Content-Type", file.getContentType());
981             Scheduler.get().scheduleDeferred(copyFile);
982         }
983         else  if (callback != null) {
984             callback.execute();
985         }
986     }
987
988     public void copySubfolders(final Iterator<Folder> iter, final String targetUsername, final String targetUri, final Command callback) {
989         if (iter.hasNext()) {
990             final Folder f = iter.next();
991             copyFolder(f, targetUsername, targetUri, new Command() {
992                                 
993                                 @Override
994                                 public void execute() {
995                                         copySubfolders(iter, targetUsername, targetUri, callback);
996                                 }
997                         });
998         }
999         else  if (callback != null) {
1000             callback.execute();
1001         }
1002     }
1003
1004     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, final Command callback) {
1005         String path = targetUri + "/" + f.getName();
1006         PutRequest createFolder = new PutRequest(getApiPath(), targetUsername, path) {
1007             @Override
1008             public void onSuccess(Resource result) {
1009                 GetRequest<Folder> getFolder = new GetRequest<Folder>(Folder.class, getApiPath(), f.getOwner(), "/" + f.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(f.getPrefix()), f) {
1010
1011                                         @Override
1012                                         public void onSuccess(final Folder _f) {
1013                                 Iterator<File> iter = _f.getFiles().iterator();
1014                                 copyFiles(iter, targetUsername, targetUri + "/" + _f.getName(), new Command() {
1015                                     @Override
1016                                     public void execute() {
1017                                         Iterator<Folder> iterf = _f.getSubfolders().iterator();
1018                                         copySubfolders(iterf, targetUsername, targetUri + "/" + _f.getName(), callback);
1019                                     }
1020                                 });
1021                                         }
1022
1023                                         @Override
1024                                         public void onError(Throwable t) {
1025                                 GWT.log("", t);
1026                                                 setError(t);
1027                                 if (t instanceof RestException) {
1028                                     displayError("Unable to get folder: " + ((RestException) t).getHttpStatusText());
1029                                 }
1030                                 else
1031                                     displayError("System error getting folder: " + t.getMessage());
1032                                         }
1033
1034                                         @Override
1035                                         protected void onUnauthorized(Response response) {
1036                                                 sessionExpired();
1037                                         }
1038                                 };
1039                                 getFolder.setHeader("X-Auth-Token", getToken());
1040                                 Scheduler.get().scheduleDeferred(getFolder);
1041             }
1042
1043             @Override
1044             public void onError(Throwable t) {
1045                 GWT.log("", t);
1046                                 setError(t);
1047                if (t instanceof RestException) {
1048                     displayError("Unable to create folder: " + ((RestException) t).getHttpStatusText());
1049                 }
1050                 else
1051                     displayError("System error creating folder: " + t.getMessage());
1052             }
1053
1054                         @Override
1055                         protected void onUnauthorized(Response response) {
1056                                 sessionExpired();
1057                         }
1058         };
1059         createFolder.setHeader("X-Auth-Token", getToken());
1060         createFolder.setHeader("Accept", "*/*");
1061         createFolder.setHeader("Content-Length", "0");
1062         createFolder.setHeader("Content-Type", "application/folder");
1063         Scheduler.get().scheduleDeferred(createFolder);
1064     }
1065     
1066     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1067         selectionModels.add(model);
1068     }
1069
1070         public OtherSharedTreeView getOtherSharedTreeView() {
1071                 return otherSharedTreeView;
1072         }
1073
1074         public void updateTrash(boolean showFiles, Command callback) {
1075                 updateFolder(trash, showFiles, callback);
1076         }
1077
1078         public void updateGroupsNode() {
1079                 groupTreeView.updateGroupNode(null);
1080         }
1081
1082         public void addGroup(String groupname) {
1083                 Group newGroup = new Group(groupname);
1084                 account.addGroup(newGroup);
1085                 groupTreeView.updateGroupNode(null);
1086         }
1087
1088         public void removeGroup(Group group) {
1089                 account.removeGroup(group);
1090                 updateGroupsNode();
1091         }
1092
1093         public TreeView getSelectedTree() {
1094                 return selectedTree;
1095         }
1096         
1097         public void setSelectedTree(TreeView selected) {
1098                 selectedTree = selected;
1099         }
1100
1101         public Folder getSelection() {
1102                 return selectedTree.getSelection();
1103         }
1104
1105         public void showFolderStatistics(int folderFileCount) {
1106                 numOfFiles.setHTML(String.valueOf(folderFileCount));
1107         }
1108
1109         public GroupTreeView getGroupTreeView() {
1110                 return groupTreeView;
1111         }
1112
1113         public void sessionExpired() {
1114                 new SessionExpiredDialog(this).center();
1115         }
1116
1117         public void updateRootFolder(Command callback) {
1118                 updateFolder(account.getPithos(), false, callback);
1119         }
1120
1121         void createMySharedTree() {
1122                 mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1123                 mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1124                     @Override
1125                     public void onSelectionChange(SelectionChangeEvent event) {
1126                         if (mysharedTreeSelectionModel.getSelectedObject() != null) {
1127                             deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
1128                             upload.setEnabled(false);
1129                             updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
1130                                         showRelevantToolbarButtons();
1131                         }
1132                                 else {
1133                                         if (getSelectedTree().equals(mysharedTreeView))
1134                                                 setSelectedTree(null);
1135                                         if (getSelectedTree() == null)
1136                                                 showRelevantToolbarButtons();
1137                                 }
1138                     }
1139                 });
1140                 selectionModels.add(mysharedTreeSelectionModel);
1141                 mysharedTreeViewModel = new MysharedTreeViewModel(Pithos.this, mysharedTreeSelectionModel);
1142                 mysharedTreeViewModel.initialize(new Command() {
1143                         
1144                         @Override
1145                         public void execute() {
1146                             mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
1147                                 trees.insert(mysharedTreeView, 2);
1148                                 treeViews.add(mysharedTreeView);
1149                                 createOtherSharedTree();
1150                         }
1151                 });
1152         }
1153
1154         void createOtherSharedTree() {
1155                 otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1156                 otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1157                     @Override
1158                     public void onSelectionChange(SelectionChangeEvent event) {
1159                         if (otherSharedTreeSelectionModel.getSelectedObject() != null) {
1160                             deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
1161                             otherSharedTreeView.addStyleName("cellTreeWidget-selectedTree");
1162                             applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
1163                             updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true);
1164                                         showRelevantToolbarButtons();
1165                         }
1166                                 else {
1167                                         if (getSelectedTree().equals(otherSharedTreeView))
1168                                                 setSelectedTree(null);
1169                                         if (getSelectedTree() == null)
1170                                                 showRelevantToolbarButtons();
1171                                 }
1172                     }
1173                 });
1174                 selectionModels.add(otherSharedTreeSelectionModel);
1175                 otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
1176                 otherSharedTreeViewModel.initialize(new Command() {
1177                         
1178                         @Override
1179                         public void execute() {
1180                             otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
1181                                 trees.insert(otherSharedTreeView, 3);
1182                                 treeViews.add(otherSharedTreeView);
1183                         }
1184                 });
1185         }
1186
1187         public native void log1(String message)/*-{
1188                 $wnd.console.log(message);
1189         }-*/;
1190
1191         public String getErrorData() {
1192                 if (error != null)
1193                         return error.toString();
1194                 return "";
1195         }
1196         
1197         public void setError(Throwable t) {
1198                 error = t;
1199         }
1200         
1201         public void showRelevantToolbarButtons() {
1202                 toolbar.showRelevantButtons();
1203         }
1204
1205         public FileUploadDialog getFileUploadDialog() {
1206                 if (fileUploadDialog == null)
1207                         fileUploadDialog = new FileUploadDialog(this);
1208                 return fileUploadDialog;
1209         }
1210
1211         public void hideUploadIndicator() {
1212                 upload.removeStyleName("pithos-uploadButton-loading");
1213                 upload.setTitle("");
1214         }
1215         
1216         public void showUploadIndicator() {
1217                 upload.addStyleName("pithos-uploadButton-loading");
1218                 upload.setTitle("Upload in progress. Click for details.");
1219         }
1220
1221         public void scheduleFolderHeadCommand(final Folder folder, final Command callback) {
1222                 if (folder == null) {
1223                         if (callback != null)
1224                                 callback.execute();
1225                 }
1226                 else {
1227                         HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getApiPath(), folder.getOwner(), folder.getUri(), folder) {
1228         
1229                                 @Override
1230                                 public void onSuccess(Folder _result) {
1231                                         if (callback != null)
1232                                                 callback.execute();
1233                                 }
1234         
1235                                 @Override
1236                                 public void onError(Throwable t) {
1237                                 if (t instanceof RestException) {
1238                                         if (((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
1239                                 final String path = folder.getUri();
1240                                 PutRequest newFolder = new PutRequest(getApiPath(), folder.getOwner(), path) {
1241                                     @Override
1242                                     public void onSuccess(Resource _result) {
1243                                         scheduleFolderHeadCommand(folder, callback);
1244                                     }
1245         
1246                                     @Override
1247                                     public void onError(Throwable _t) {
1248                                         GWT.log("", _t);
1249                                                                 setError(_t);
1250                                         if(_t instanceof RestException){
1251                                             displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
1252                                         }
1253                                         else
1254                                             displayError("System error creating folder: " + _t.getMessage());
1255                                     }
1256         
1257                                                 @Override
1258                                                 protected void onUnauthorized(Response response) {
1259                                                         sessionExpired();
1260                                                 }
1261                                 };
1262                                 newFolder.setHeader("X-Auth-Token", getToken());
1263                                 newFolder.setHeader("Content-Type", "application/folder");
1264                                 newFolder.setHeader("Accept", "*/*");
1265                                 newFolder.setHeader("Content-Length", "0");
1266                                 Scheduler.get().scheduleDeferred(newFolder);
1267                                         }
1268                                         else
1269                                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
1270                                 }
1271                                 else
1272                                     displayError("System error heading folder: " + t.getMessage());
1273         
1274                                 GWT.log("Error heading folder", t);
1275                                         setError(t);
1276                                 }
1277         
1278                                 @Override
1279                                 protected void onUnauthorized(Response response) {
1280                                         sessionExpired();
1281                                 }
1282                         };
1283                         headFolder.setHeader("X-Auth-Token", getToken());
1284                         Scheduler.get().scheduleDeferred(headFolder);
1285                 }
1286         }
1287
1288         public void scheduleFileHeadCommand(File f, final Command callback) {
1289                 HeadRequest<File> headFile = new HeadRequest<File>(File.class, getApiPath(), f.getOwner(), f.getUri(), f) {
1290
1291                         @Override
1292                         public void onSuccess(File _result) {
1293                                 if (callback != null)
1294                                         callback.execute();
1295                         }
1296
1297                         @Override
1298                         public void onError(Throwable t) {
1299                         GWT.log("Error heading file", t);
1300                                 setError(t);
1301                         if (t instanceof RestException)
1302                             displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
1303                         else
1304                             displayError("System error heading file: " + t.getMessage());
1305                         }
1306
1307                         @Override
1308                         protected void onUnauthorized(Response response) {
1309                                 sessionExpired();
1310                         }
1311                 };
1312                 headFile.setHeader("X-Auth-Token", getToken());
1313                 Scheduler.get().scheduleDeferred(headFile);
1314         }
1315 }