9ff7b2bcc99f07e4260d1d6a1f7ba0c25e22b4ad
[pithos-web-client] / src / gr / grnet / pithos / web / client / Pithos.java
1 /*
2  * Copyright 2011-2013 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 com.google.gwt.core.client.EntryPoint;
38 import com.google.gwt.core.client.GWT;
39 import com.google.gwt.core.client.JsArrayString;
40 import com.google.gwt.core.client.Scheduler;
41 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
42 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
43 import com.google.gwt.event.dom.client.ClickEvent;
44 import com.google.gwt.event.dom.client.ClickHandler;
45 import com.google.gwt.event.logical.shared.ResizeEvent;
46 import com.google.gwt.event.logical.shared.ResizeHandler;
47 import com.google.gwt.http.client.Response;
48 import com.google.gwt.http.client.URL;
49 import com.google.gwt.i18n.client.DateTimeFormat;
50 import com.google.gwt.i18n.client.Dictionary;
51 import com.google.gwt.i18n.client.TimeZone;
52 import com.google.gwt.resources.client.ClientBundle;
53 import com.google.gwt.resources.client.CssResource;
54 import com.google.gwt.resources.client.ImageResource;
55 import com.google.gwt.resources.client.ImageResource.ImageOptions;
56 import com.google.gwt.user.client.*;
57 import com.google.gwt.user.client.ui.*;
58 import com.google.gwt.view.client.SelectionChangeEvent;
59 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
60 import com.google.gwt.view.client.SingleSelectionModel;
61 import gr.grnet.pithos.web.client.catalog.UpdateUserCatalogs;
62 import gr.grnet.pithos.web.client.catalog.UserCatalogs;
63 import gr.grnet.pithos.web.client.commands.UploadFileCommand;
64 import gr.grnet.pithos.web.client.foldertree.*;
65 import gr.grnet.pithos.web.client.grouptree.Group;
66 import gr.grnet.pithos.web.client.grouptree.GroupTreeView;
67 import gr.grnet.pithos.web.client.grouptree.GroupTreeViewModel;
68 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeView;
69 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeViewModel;
70 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeView;
71 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeViewModel;
72 import gr.grnet.pithos.web.client.rest.*;
73 import org.apache.http.HttpStatus;
74
75 import java.util.*;
76
77 /**
78  * Entry point classes define <code>onModuleLoad()</code>.
79  */
80 public class Pithos implements EntryPoint, ResizeHandler {
81     private static final boolean IsLOGEnabled = false;
82     public static final boolean IsDetailedHTTPLOGEnabled = true;
83     public static final boolean IsFullResponseBodyLOGEnabled = true;
84
85     public static final Set<String> HTTPHeadersToIgnoreInLOG = new HashSet<String>();
86     static {
87         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_CONNECTION);
88         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_DATE);
89         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_KEEP_ALIVE);
90         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_SERVER);
91         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_VARY);
92         HTTPHeadersToIgnoreInLOG.add(Const.IF_MODIFIED_SINCE);
93     }
94
95     public static final Configuration config = GWT.create(Configuration.class);
96     public static final String CONFIG_API_PATH = getFromOtherPropertiesOrDefaultString("STORAGE_API_URL", config.apiPath());
97     static {
98         LOG("CONFIG_API_PATH = ", CONFIG_API_PATH);
99     }
100
101     public static final Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
102     public static String getFromOtherPropertiesOrNull(String key) {
103         try {
104             return otherProperties.get(key);
105         }
106         catch(Exception e) {
107             LOGError(e);
108             return null;
109         }
110     }
111     public static String getFromOtherPropertiesOrDefaultString(String key, String dflt) {
112         try {
113             return otherProperties.get(key);
114         }
115         catch(Exception e) {
116             return dflt;
117         }
118     }
119     public static final String OTHERPROPS_STORAGE_API_URL = getFromOtherPropertiesOrNull("STORAGE_API_URL");
120     public static final String OTHERPROPS_USER_CATALOGS_API_URL = getFromOtherPropertiesOrNull("USER_CATALOGS_API_URL");
121     static {
122         LOG("STORAGE_API_URL = ", OTHERPROPS_STORAGE_API_URL);
123         LOG("USER_CATALOGS_API_URL = ", OTHERPROPS_USER_CATALOGS_API_URL);
124     }
125
126     public static final String STORAGE_API_URL;
127     static {
128         if(OTHERPROPS_STORAGE_API_URL != null) {
129             STORAGE_API_URL = OTHERPROPS_STORAGE_API_URL;
130         }
131         else if(CONFIG_API_PATH != null) {
132             STORAGE_API_URL = CONFIG_API_PATH;
133         }
134         else {
135             throw new RuntimeException("Unknown STORAGE_API_URL");
136         }
137     }
138     public static final String USER_CATALOGS_API_URL;
139     static {
140         if(OTHERPROPS_USER_CATALOGS_API_URL != null) {
141             USER_CATALOGS_API_URL = OTHERPROPS_USER_CATALOGS_API_URL;
142         }
143         else if(OTHERPROPS_STORAGE_API_URL != null) {
144             throw new RuntimeException("STORAGE_API_URL is defined but USER_CATALOGS_API_URL is not");
145         }
146         else {
147             // https://server.com/v1/ --> https://server.com
148             String url = CONFIG_API_PATH;
149             url = Helpers.stripTrailing(url, "/");
150             url = Helpers.upToIncludingLastPart(url, "/");
151             url = Helpers.stripTrailing(url, "/");
152             url = url + "/user_catalogs";
153
154             USER_CATALOGS_API_URL = url;
155
156             LOG("USER_CATALOGS_API_URL = ", USER_CATALOGS_API_URL);
157         }
158     }
159
160     public interface Style extends CssResource {
161         String commandAnchor();
162
163         String statistics();
164
165         @ClassName("gwt-HTML")
166         String html();
167
168         String uploadAlert();
169
170         String uploadAlertLink();
171
172         String uploadAlertProgress();
173
174         String uploadAlertPercent();
175
176         String uploadAlertClose();
177     }
178
179     public interface Resources extends ClientBundle {
180         @Source("Pithos.css")
181         Style pithosCss();
182
183         @Source("gr/grnet/pithos/resources/close-popup.png")
184         ImageResource closePopup();
185     }
186
187     public static Resources resources = GWT.create(Resources.class);
188
189     /**
190      * Instantiate an application-level image bundle. This object will provide
191      * programmatic access to all the images needed by widgets.
192      */
193     static Images images = (Images) GWT.create(Images.class);
194
195     public String getUserID() {
196         return userID;
197     }
198
199     public UserCatalogs getUserCatalogs() {
200         return userCatalogs;
201     }
202
203     public String getCurrentUserDisplayNameOrID() {
204         final String displayName = userCatalogs.getDisplayName(getUserID());
205         return displayName == null ? getUserID() : displayName;
206     }
207
208     public boolean hasDisplayNameForUserID(String userID) {
209         return userCatalogs.getDisplayName(userID) != null;
210     }
211
212     public boolean hasIDForUserDisplayName(String userDisplayName) {
213         return userCatalogs.getID(userDisplayName) != null;
214     }
215
216     public String getDisplayNameForUserID(String userID) {
217         return userCatalogs.getDisplayName(userID);
218     }
219
220     public String getIDForUserDisplayName(String userDisplayName) {
221         return userCatalogs.getID(userDisplayName);
222     }
223
224     public List<String> getDisplayNamesForUserIDs(List<String> userIDs) {
225         if(userIDs == null) {
226             userIDs = new ArrayList<String>();
227         }
228         final List<String> userDisplayNames = new ArrayList<String>();
229         for(String userID : userIDs) {
230             final String displayName = getDisplayNameForUserID(userID);
231             userDisplayNames.add(displayName);
232         }
233
234         return userDisplayNames;
235     }
236
237     public List<String> filterUserIDsWithUnknownDisplayName(Collection<String> userIDs) {
238         if(userIDs == null) {
239             userIDs = new ArrayList<String>();
240         }
241         final List<String> filtered = new ArrayList<String>();
242         for(String userID : userIDs) {
243             if(!this.userCatalogs.hasID(userID)) {
244                 filtered.add(userID);
245             }
246         }
247         return filtered;
248     }
249
250     public void setAccount(AccountResource acct) {
251         account = acct;
252     }
253
254     public AccountResource getAccount() {
255         return account;
256     }
257
258     public void updateFolder(Folder f, boolean showfiles, Command callback, final boolean openParent) {
259         folderTreeView.updateFolder(f, showfiles, callback, openParent);
260     }
261
262     public void updateGroupNode(Group group) {
263         groupTreeView.updateGroupNode(group);
264     }
265
266     public void updateMySharedRoot() {
267         mysharedTreeView.updateRoot();
268     }
269
270     public void updateSharedFolder(Folder f, boolean showfiles, Command callback) {
271         mysharedTreeView.updateFolder(f, showfiles, callback);
272     }
273
274     public void updateSharedFolder(Folder f, boolean showfiles) {
275         updateSharedFolder(f, showfiles, null);
276     }
277
278     public void updateOtherSharedFolder(Folder f, boolean showfiles, Command callback) {
279         otherSharedTreeView.updateFolder(f, showfiles, callback);
280     }
281
282     public MysharedTreeView getMySharedTreeView() {
283         return mysharedTreeView;
284     }
285
286     /**
287      * An aggregate image bundle that pulls together all the images for this
288      * application into a single bundle.
289      */
290     public interface Images extends TopPanel.Images, FileList.Images, ToolsMenu.Images {
291
292         @Source("gr/grnet/pithos/resources/document.png")
293         ImageResource folders();
294
295         @Source("gr/grnet/pithos/resources/advancedsettings.png")
296         @ImageOptions(width = 32, height = 32)
297         ImageResource tools();
298     }
299
300     private Throwable error;
301
302     /**
303      * The Application Clipboard implementation;
304      */
305     private Clipboard clipboard = new Clipboard();
306
307     /**
308      * The top panel that contains the menu bar.
309      */
310     private TopPanel topPanel;
311
312     /**
313      * The panel that contains the various system messages.
314      */
315     private MessagePanel messagePanel = new MessagePanel(this, Pithos.images);
316
317     /**
318      * The bottom panel that contains the status bar.
319      */
320     StatusPanel statusPanel = null;
321
322     /**
323      * The file list widget.
324      */
325     private FileList fileList;
326
327     /**
328      * The tab panel that occupies the right side of the screen.
329      */
330     private VerticalPanel inner = new VerticalPanel();
331
332
333     /**
334      * The split panel that will contain the left and right panels.
335      */
336     private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
337
338     /**
339      * The currently selected item in the application, for use by the Edit menu
340      * commands. Potential types are Folder, File, User and Group.
341      */
342     private Object currentSelection;
343
344     public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
345
346     /**
347      * The ID that uniquely identifies the user in Pithos+.
348      * Currently this is a UUID. It used to be the user's email.
349      */
350     private String userID = null;
351
352     /**
353      * Holds mappings from user UUIDs to emails and vice-versa.
354      */
355     private UserCatalogs userCatalogs = new UserCatalogs();
356
357     /**
358      * The authentication token of the current user.
359      */
360     private String userToken;
361
362     VerticalPanel trees;
363
364     SingleSelectionModel<Folder> folderTreeSelectionModel;
365     FolderTreeViewModel folderTreeViewModel;
366     FolderTreeView folderTreeView;
367
368     SingleSelectionModel<Folder> mysharedTreeSelectionModel;
369     MysharedTreeViewModel mysharedTreeViewModel;
370     MysharedTreeView mysharedTreeView = null;
371
372     protected SingleSelectionModel<Folder> otherSharedTreeSelectionModel;
373     OtherSharedTreeViewModel otherSharedTreeViewModel;
374     OtherSharedTreeView otherSharedTreeView = null;
375
376     GroupTreeViewModel groupTreeViewModel;
377     GroupTreeView groupTreeView;
378
379     TreeView selectedTree;
380     protected AccountResource account;
381
382     Folder trash;
383
384     List<Composite> treeViews = new ArrayList<Composite>();
385
386     @SuppressWarnings("rawtypes")
387     List<SingleSelectionModel> selectionModels = new ArrayList<SingleSelectionModel>();
388
389     public Button upload;
390
391     private HTML numOfFiles;
392
393     private Toolbar toolbar;
394
395     private FileUploadDialog fileUploadDialog = new FileUploadDialog(this);
396
397     UploadAlert uploadAlert;
398
399     Date lastModified;
400
401     @Override
402     public void onModuleLoad() {
403         if(parseUserCredentials()) {
404             initialize();
405         }
406     }
407
408     static native void __ConsoleLog(String message) /*-{
409       try { console.log(message); } catch (e) {}
410     }-*/;
411
412     public static void LOGError(Throwable error, StringBuilder sb) {
413         if(!isLOGEnabled()) { return; }
414
415         sb.append("\nException: [" + error.toString().replace("\n", "\n  ") + "]");
416         Throwable cause = error.getCause();
417         if(cause != null) {
418             sb.append("\nCauses:\n");
419             while(cause != null) {
420                 sb.append("  ");
421                 sb.append("[" + cause.toString().replace("\n", "\n  ")  + "]");
422                 sb.append("\n");
423                 cause = cause.getCause();
424             }
425         }
426         else {
427             sb.append("\n");
428         }
429
430         StackTraceElement[] stackTrace = error.getStackTrace();
431         sb.append("Stack trace (" + stackTrace.length + " elements):\n");
432         for(int i = 0; i < stackTrace.length; i++) {
433             StackTraceElement errorElem = stackTrace[i];
434             sb.append("  [" + i + "] ");
435             sb.append(errorElem.toString());
436             sb.append("\n");
437         }
438     }
439
440     public static void LOGError(Throwable error) {
441         if(!isLOGEnabled()) { return; }
442
443         final StringBuilder sb = new StringBuilder();
444         LOGError(error, sb);
445         if(sb.length() > 0) {
446             __ConsoleLog(sb.toString());
447         }
448     }
449
450     public static boolean isLOGEnabled() {
451         return IsLOGEnabled;
452     }
453
454     public static void LOG(Object ...args) {
455         if(!isLOGEnabled()) { return; }
456
457         final StringBuilder sb = new StringBuilder();
458         for(Object arg : args) {
459             if(arg instanceof Throwable) {
460                 LOGError((Throwable) arg, sb);
461             }
462             else {
463                 sb.append(arg);
464             }
465         }
466
467         if(sb.length() > 0) {
468             __ConsoleLog(sb.toString());
469         }
470     }
471
472     private void initialize() {
473         userCatalogs.updateWithIDAndName("*", "All Pithos users");
474
475         lastModified = new Date(); //Initialize if-modified-since value with now.
476         resources.pithosCss().ensureInjected();
477         boolean bareContent = Window.Location.getParameter("noframe") != null;
478         String contentWidth = bareContent ? Const.PERCENT_100 : Const.PERCENT_75;
479
480         VerticalPanel outer = new VerticalPanel();
481         outer.setWidth(Const.PERCENT_100);
482         if(!bareContent) {
483             outer.addStyleName("pithos-outer");
484         }
485
486         if(!bareContent) {
487             topPanel = new TopPanel(this, Pithos.images);
488             topPanel.setWidth(Const.PERCENT_100);
489             outer.add(topPanel);
490             outer.setCellHorizontalAlignment(topPanel, HasHorizontalAlignment.ALIGN_CENTER);
491         }
492
493         messagePanel.setVisible(false);
494         outer.add(messagePanel);
495         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
496         outer.setCellVerticalAlignment(messagePanel, HasVerticalAlignment.ALIGN_MIDDLE);
497
498         HorizontalPanel header = new HorizontalPanel();
499         header.addStyleName("pithos-header");
500         header.setWidth(contentWidth);
501         if(bareContent) {
502             header.addStyleName("pithos-header-noframe");
503         }
504         upload = new Button("Upload", new ClickHandler() {
505             @Override
506             public void onClick(ClickEvent event) {
507                 if(getSelection() != null) {
508                     new UploadFileCommand(Pithos.this, null, getSelection()).execute();
509                 }
510             }
511         });
512         upload.addStyleName("pithos-uploadButton");
513         header.add(upload);
514         header.setCellHorizontalAlignment(upload, HasHorizontalAlignment.ALIGN_LEFT);
515         header.setCellVerticalAlignment(upload, HasVerticalAlignment.ALIGN_MIDDLE);
516
517         toolbar = new Toolbar(this);
518         header.add(toolbar);
519         header.setCellHorizontalAlignment(toolbar, HasHorizontalAlignment.ALIGN_CENTER);
520         header.setCellVerticalAlignment(toolbar, HasVerticalAlignment.ALIGN_MIDDLE);
521
522         HorizontalPanel folderStatistics = new HorizontalPanel();
523         folderStatistics.addStyleName("pithos-folderStatistics");
524         numOfFiles = new HTML();
525         folderStatistics.add(numOfFiles);
526         folderStatistics.setCellVerticalAlignment(numOfFiles, HasVerticalAlignment.ALIGN_MIDDLE);
527         HTML numOfFilesLabel = new HTML("&nbsp;Files");
528         folderStatistics.add(numOfFilesLabel);
529         folderStatistics.setCellVerticalAlignment(numOfFilesLabel, HasVerticalAlignment.ALIGN_MIDDLE);
530         header.add(folderStatistics);
531         header.setCellHorizontalAlignment(folderStatistics, HasHorizontalAlignment.ALIGN_RIGHT);
532         header.setCellVerticalAlignment(folderStatistics, HasVerticalAlignment.ALIGN_MIDDLE);
533         header.setCellWidth(folderStatistics, "40px");
534         outer.add(header);
535         outer.setCellHorizontalAlignment(header, HasHorizontalAlignment.ALIGN_CENTER);
536         // Inner contains the various lists
537         inner.sinkEvents(Event.ONCONTEXTMENU);
538         inner.setWidth(Const.PERCENT_100);
539
540         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
541         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
542             @Override
543             public void onSelectionChange(SelectionChangeEvent event) {
544                 if(folderTreeSelectionModel.getSelectedObject() != null) {
545                     deselectOthers(folderTreeView, folderTreeSelectionModel);
546                     applyPermissions(folderTreeSelectionModel.getSelectedObject());
547                     Folder f = folderTreeSelectionModel.getSelectedObject();
548                     updateFolder(f, true, new Command() {
549
550                         @Override
551                         public void execute() {
552                             updateStatistics();
553                         }
554                     }, true);
555                     showRelevantToolbarButtons();
556                 }
557                 else {
558                     if(getSelectedTree().equals(folderTreeView)) {
559                         setSelectedTree(null);
560                     }
561                     if(getSelectedTree() == null) {
562                         showRelevantToolbarButtons();
563                     }
564                 }
565             }
566         });
567         selectionModels.add(folderTreeSelectionModel);
568
569         folderTreeViewModel = new FolderTreeViewModel(this, folderTreeSelectionModel);
570         folderTreeView = new FolderTreeView(folderTreeViewModel);
571         treeViews.add(folderTreeView);
572
573         fileList = new FileList(this, images);
574         inner.add(fileList);
575
576         trees = new VerticalPanel();
577         trees.setWidth(Const.PERCENT_100);
578
579         // Add the left and right panels to the split panel.
580         splitPanel.setLeftWidget(trees);
581         FlowPanel right = new FlowPanel();
582         right.getElement().setId("rightPanel");
583         right.add(inner);
584         splitPanel.setRightWidget(right);
585         splitPanel.setSplitPosition("219px");
586         splitPanel.setSize(Const.PERCENT_100, Const.PERCENT_100);
587         splitPanel.addStyleName("pithos-splitPanel");
588         splitPanel.setWidth(contentWidth);
589         outer.add(splitPanel);
590         outer.setCellHorizontalAlignment(splitPanel, HasHorizontalAlignment.ALIGN_CENTER);
591
592         if(!bareContent) {
593             statusPanel = new StatusPanel();
594             statusPanel.setWidth(Const.PERCENT_100);
595             outer.add(statusPanel);
596             outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
597         }
598         else {
599             splitPanel.addStyleName("pithos-splitPanel-noframe");
600         }
601
602         // Hook the window resize event, so that we can adjust the UI.
603         Window.addResizeHandler(this);
604         // Clear out the window's built-in margin, because we want to take
605         // advantage of the entire client area.
606         Window.setMargin("0px");
607         // Finally, add the outer panel to the RootPanel, so that it will be
608         // displayed.
609         RootPanel.get().add(outer);
610         // Call the window resized handler to get the initial sizes setup. Doing
611         // this in a deferred command causes it to occur after all widgets'
612         // sizes have been computed by the browser.
613         Scheduler.get().scheduleIncremental(new RepeatingCommand() {
614
615             @Override
616             public boolean execute() {
617                 if(!isCloudbarReady()) {
618                     return true;
619                 }
620                 onWindowResized(Window.getClientHeight());
621                 return false;
622             }
623         });
624
625         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
626             @Override
627             public void execute() {
628                 LOG("Pithos::initialize() Calling Pithos::fetchAccount()");
629                 fetchAccount(new Command() {
630
631                     @Override
632                     public void execute() {
633                         if(!account.hasHomeContainer()) {
634                             createHomeContainer(account, this);
635                         }
636                         else if(!account.hasTrashContainer()) {
637                             createTrashContainer(this);
638                         }
639                         else {
640                             for(Folder f : account.getContainers()) {
641                                 if(f.getName().equals(Const.TRASH_CONTAINER)) {
642                                     trash = f;
643                                     break;
644                                 }
645                             }
646                             trees.add(folderTreeView);
647                             folderTreeViewModel.initialize(account, new Command() {
648
649                                 @Override
650                                 public void execute() {
651                                     createMySharedTree();
652                                 }
653                             });
654
655                             HorizontalPanel separator = new HorizontalPanel();
656                             separator.addStyleName("pithos-statisticsSeparator");
657                             separator.add(new HTML(""));
658                             trees.add(separator);
659
660                             groupTreeViewModel = new GroupTreeViewModel(Pithos.this);
661                             groupTreeView = new GroupTreeView(groupTreeViewModel);
662                             treeViews.add(groupTreeView);
663                             trees.add(groupTreeView);
664                             folderTreeView.showStatistics(account);
665                         }
666                     }
667                 });
668             }
669         });
670     }
671
672     public void scheduleResfresh() {
673         Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
674
675             @Override
676             public boolean execute() {
677                 final Folder f = getSelection();
678                 if(f == null) {
679                     return true;
680                 }
681
682                 HeadRequest<Folder> head = new HeadRequest<Folder>(Folder.class, getStorageAPIURL(), f.getOwnerID(), "/" + f.getContainer()) {
683
684                     @Override
685                     public void onSuccess(Folder _result) {
686                         lastModified = new Date();
687                         if(getSelectedTree().equals(folderTreeView)) {
688                             updateFolder(f, true, new Command() {
689
690                                 @Override
691                                 public void execute() {
692                                     scheduleResfresh();
693                                 }
694
695                             }, false);
696                         }
697                         else if(getSelectedTree().equals(mysharedTreeView)) {
698                             updateSharedFolder(f, true, new Command() {
699
700                                 @Override
701                                 public void execute() {
702                                     scheduleResfresh();
703                                 }
704                             });
705                         }
706                         else {
707                             scheduleResfresh();
708                         }
709                     }
710
711                     @Override
712                     public void onError(Throwable t) {
713                         if(t instanceof RestException && ((RestException) t).getHttpStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
714                             scheduleResfresh();
715                         }
716                         else if(retries >= MAX_RETRIES) {
717                             LOG("Error heading folder. ", t);
718                             setError(t);
719                             if(t instanceof RestException) {
720                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
721                             }
722                             else {
723                                 displayError("System error heading folder: " + t.getMessage());
724                             }
725                         }
726                         else {//retry
727                             LOG("Retry ", retries);
728                             Scheduler.get().scheduleDeferred(this);
729                         }
730                     }
731
732                     @Override
733                     protected void onUnauthorized(Response response) {
734                         if(retries >= MAX_RETRIES) {
735                             sessionExpired();
736                         }
737                         else //retry
738                         {
739                             Scheduler.get().scheduleDeferred(this);
740                         }
741                     }
742                 };
743                 head.setHeader(Const.X_AUTH_TOKEN, getUserToken());
744                 head.setHeader(Const.IF_MODIFIED_SINCE, DateTimeFormat.getFormat(Const.DATE_FORMAT_1).format(lastModified, TimeZone.createTimeZone(0)) + " GMT");
745                 Scheduler.get().scheduleDeferred(head);
746
747                 return false;
748             }
749         }, 3000);
750     }
751
752     public void applyPermissions(Folder f) {
753         if(f != null) {
754             if(f.isInTrash()) {
755                 upload.setEnabled(false);
756                 disableUploadArea();
757             }
758             else {
759                 Boolean[] perms = f.getPermissions().get(userID);
760                 if(f.getOwnerID().equals(userID) || (perms != null && perms[1] != null && perms[1])) {
761                     upload.setEnabled(true);
762                     enableUploadArea();
763                 }
764                 else {
765                     upload.setEnabled(false);
766                     disableUploadArea();
767                 }
768             }
769         }
770         else {
771             upload.setEnabled(false);
772             disableUploadArea();
773         }
774     }
775
776     @SuppressWarnings({"rawtypes", "unchecked"})
777     public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
778         selectedTree = _selectedTree;
779
780         for(SingleSelectionModel s : selectionModels) {
781             if(!s.equals(model) && s.getSelectedObject() != null) {
782                 s.setSelected(s.getSelectedObject(), false);
783             }
784         }
785     }
786
787     public void showFiles(final Folder f) {
788         Set<File> files = f.getFiles();
789         showFiles(files);
790     }
791
792     public void showFiles(Set<File> files) {
793         fileList.setFiles(new ArrayList<File>(files));
794     }
795
796     /**
797      * Parse and store the user credentials to the appropriate fields.
798      */
799     private boolean parseUserCredentials() {
800         final String cookie = otherProperties.get(Const.AUTH_COOKIE);
801         String auth = Cookies.getCookie(cookie);
802         if(auth == null) {
803             authenticateUser();
804             return false;
805         }
806         if(auth.startsWith("\"")) {
807             auth = auth.substring(1);
808         }
809         if(auth.endsWith("\"")) {
810             auth = auth.substring(0, auth.length() - 1);
811         }
812         String[] authSplit = auth.split("\\" + config.cookieSeparator(), 2);
813         if(authSplit.length != 2) {
814             authenticateUser();
815             return false;
816         }
817         this.userID = authSplit[0];
818         this.userToken = authSplit[1];
819
820         String gotoUrl = Window.Location.getParameter("goto");
821         if(gotoUrl != null && gotoUrl.length() > 0) {
822             Window.Location.assign(gotoUrl);
823             return false;
824         }
825         return true;
826     }
827
828     /**
829      * Redirect the user to the login page for authentication.
830      */
831     protected void authenticateUser() {
832         Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
833         Window.Location.assign(otherProperties.get(Const.LOGIN_URL) + Window.Location.getHref());
834     }
835
836     public void fetchAccount(final Command callback) {
837         String path = "?format=json";
838
839         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getStorageAPIURL(), userID, path) {
840             @Override
841             public void onSuccess(AccountResource accountResource) {
842                 account = accountResource;
843                 if(callback != null) {
844                     callback.execute();
845                 }
846
847                 final List<String> memberIDs = new ArrayList<String>();
848                 final List<Group> groups = account.getGroups();
849                 for(Group group : groups) {
850                     memberIDs.addAll(group.getMemberIDs());
851                 }
852                 memberIDs.add(Pithos.this.getUserID());
853
854                 final List<String> theUnknown = Pithos.this.filterUserIDsWithUnknownDisplayName(memberIDs);
855                 // Initialize the user catalog
856                 new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();
857                 LOG("Called new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();");
858             }
859
860             @Override
861             public void onError(Throwable t) {
862                 LOG("Error getting account", t);
863                 setError(t);
864                 if(t instanceof RestException) {
865                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
866                 }
867                 else {
868                     displayError("System error fetching user data: " + t.getMessage());
869                 }
870             }
871
872             @Override
873             protected void onUnauthorized(Response response) {
874                 sessionExpired();
875             }
876         };
877         getAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
878         Scheduler.get().scheduleDeferred(getAccount);
879     }
880
881     public void updateStatistics() {
882         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getStorageAPIURL(), userID, "", account) {
883
884             @Override
885             public void onSuccess(AccountResource _result) {
886                 folderTreeView.showStatistics(account);
887             }
888
889             @Override
890             public void onError(Throwable t) {
891                 LOG("Error getting account", t);
892                 setError(t);
893                 if(t instanceof RestException) {
894                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
895                 }
896                 else {
897                     displayError("System error fetching user data: " + t.getMessage());
898                 }
899             }
900
901             @Override
902             protected void onUnauthorized(Response response) {
903                 sessionExpired();
904             }
905         };
906         headAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
907         Scheduler.get().scheduleDeferred(headAccount);
908     }
909
910     protected void createHomeContainer(final AccountResource _account, final Command callback) {
911         String path = "/" + Const.HOME_CONTAINER;
912         PutRequest createPithos = new PutRequest(getStorageAPIURL(), getUserID(), path) {
913             @Override
914             public void onSuccess(Resource result) {
915                 if(!_account.hasTrashContainer()) {
916                     createTrashContainer(callback);
917                 }
918                 else {
919                     fetchAccount(callback);
920                 }
921             }
922
923             @Override
924             public void onError(Throwable t) {
925                 LOG("Error creating pithos", t);
926                 setError(t);
927                 if(t instanceof RestException) {
928                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
929                 }
930                 else {
931                     displayError("System error Error creating pithos: " + t.getMessage());
932                 }
933             }
934
935             @Override
936             protected void onUnauthorized(Response response) {
937                 sessionExpired();
938             }
939         };
940         createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
941         Scheduler.get().scheduleDeferred(createPithos);
942     }
943
944     protected void createTrashContainer(final Command callback) {
945         String path = "/" + Const.TRASH_CONTAINER;
946         PutRequest createPithos = new PutRequest(getStorageAPIURL(), getUserID(), path) {
947             @Override
948             public void onSuccess(Resource result) {
949                 fetchAccount(callback);
950             }
951
952             @Override
953             public void onError(Throwable t) {
954                 LOG("Error creating pithos", t);
955                 setError(t);
956                 if(t instanceof RestException) {
957                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
958                 }
959                 else {
960                     displayError("System error Error creating pithos: " + t.getMessage());
961                 }
962             }
963
964             @Override
965             protected void onUnauthorized(Response response) {
966                 sessionExpired();
967             }
968         };
969         createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
970         Scheduler.get().scheduleDeferred(createPithos);
971     }
972
973     /**
974      * Creates an HTML fragment that places an image & caption together, for use
975      * in a group header.
976      *
977      * @param imageProto an image prototype for an image
978      * @param caption    the group caption
979      * @return the header HTML fragment
980      */
981     private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
982         String captionHTML = "<table class='caption' cellpadding='0' "
983             + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML()
984             + "</td><td id =" + caption + " class='rcaption'><b style='white-space:nowrap'>&nbsp;"
985             + caption + "</b></td></tr></table>";
986         return captionHTML;
987     }
988
989     protected void onWindowResized(int height) {
990         // Adjust the split panel to take up the available room in the window.
991         int newHeight = height - splitPanel.getAbsoluteTop() - 153;
992         if(newHeight < 1) {
993             newHeight = 1;
994         }
995         splitPanel.setHeight("" + newHeight);
996         inner.setHeight("" + newHeight);
997     }
998
999     native boolean isCloudbarReady()/*-{
1000       if ($wnd.$("div.cloudbar") && $wnd.$("div.cloudbar").height() > 0)
1001         return true;
1002       return false;
1003     }-*/;
1004
1005     @Override
1006     public void onResize(ResizeEvent event) {
1007         int height = event.getHeight();
1008         onWindowResized(height);
1009     }
1010
1011     /**
1012      * Display an error message.
1013      *
1014      * @param msg the message to display
1015      */
1016     public void displayError(String msg) {
1017         messagePanel.displayError(msg);
1018         onWindowResized(Window.getClientHeight());
1019     }
1020
1021     /**
1022      * Display a warning message.
1023      *
1024      * @param msg the message to display
1025      */
1026     public void displayWarning(String msg) {
1027         messagePanel.displayWarning(msg);
1028         onWindowResized(Window.getClientHeight());
1029     }
1030
1031     /**
1032      * Display an informational message.
1033      *
1034      * @param msg the message to display
1035      */
1036     public void displayInformation(String msg) {
1037         messagePanel.displayInformation(msg);
1038         onWindowResized(Window.getClientHeight());
1039     }
1040
1041     /**
1042      * Retrieve the fileList.
1043      *
1044      * @return the fileList
1045      */
1046     public FileList getFileList() {
1047         return fileList;
1048     }
1049
1050     /**
1051      * Retrieve the topPanel.
1052      *
1053      * @return the topPanel
1054      */
1055     TopPanel getTopPanel() {
1056         return topPanel;
1057     }
1058
1059     /**
1060      * Retrieve the clipboard.
1061      *
1062      * @return the clipboard
1063      */
1064     public Clipboard getClipboard() {
1065         return clipboard;
1066     }
1067
1068     public StatusPanel getStatusPanel() {
1069         return statusPanel;
1070     }
1071
1072     public String getUserToken() {
1073         return userToken;
1074     }
1075
1076     public static native void preventIESelection() /*-{
1077       $doc.body.onselectstart = function () {
1078         return false;
1079       };
1080     }-*/;
1081
1082     public static native void enableIESelection() /*-{
1083       if ($doc.body.onselectstart != null)
1084         $doc.body.onselectstart = null;
1085     }-*/;
1086
1087     public static String getStorageAPIURL() {
1088         return STORAGE_API_URL;
1089     }
1090
1091     public static String getUserCatalogsURL() {
1092         return USER_CATALOGS_API_URL;
1093     }
1094
1095     /**
1096      * History support for folder navigation
1097      * adds a new browser history entry
1098      *
1099      * @param key
1100      */
1101     public void updateHistory(String key) {
1102 //              Replace any whitespace of the initial string to "+"
1103 //              String result = key.replaceAll("\\s","+");
1104 //              Add a new browser history entry.
1105 //              History.newItem(result);
1106         History.newItem(key);
1107     }
1108
1109     public void deleteFolder(final Folder folder, final Command callback) {
1110         final PleaseWaitPopup pwp = new PleaseWaitPopup();
1111         pwp.center();
1112         String path = "/" + folder.getContainer() + "/" + folder.getPrefix() + "?delimiter=/" + "&t=" + System.currentTimeMillis();
1113         DeleteRequest deleteFolder = new DeleteRequest(getStorageAPIURL(), folder.getOwnerID(), path) {
1114
1115             @Override
1116             protected void onUnauthorized(Response response) {
1117                 pwp.hide();
1118                 sessionExpired();
1119             }
1120
1121             @Override
1122             public void onSuccess(Resource result) {
1123                 updateFolder(folder.getParent(), true, new Command() {
1124
1125                     @Override
1126                     public void execute() {
1127                         folderTreeSelectionModel.setSelected(folder.getParent(), true);
1128                         updateStatistics();
1129                         if(callback != null) {
1130                             callback.execute();
1131                         }
1132                         pwp.hide();
1133                     }
1134                 }, true);
1135             }
1136
1137             @Override
1138             public void onError(Throwable t) {
1139                 LOG(t);
1140                 setError(t);
1141                 if(t instanceof RestException) {
1142                     if(((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND) {
1143                         displayError("Unable to delete folder: " + ((RestException) t).getHttpStatusText());
1144                     }
1145                     else {
1146                         onSuccess(null);
1147                     }
1148                 }
1149                 else {
1150                     displayError("System error unable to delete folder: " + t.getMessage());
1151                 }
1152                 pwp.hide();
1153             }
1154         };
1155         deleteFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1156         Scheduler.get().scheduleDeferred(deleteFolder);
1157     }
1158
1159     public FolderTreeView getFolderTreeView() {
1160         return folderTreeView;
1161     }
1162
1163     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
1164         if(iter.hasNext()) {
1165             File file = iter.next();
1166             String path = targetUri + "/" + file.getName();
1167             PutRequest copyFile = new PutRequest(getStorageAPIURL(), targetUsername, path) {
1168                 @Override
1169                 public void onSuccess(Resource result) {
1170                     copyFiles(iter, targetUsername, targetUri, callback);
1171                 }
1172
1173                 @Override
1174                 public void onError(Throwable t) {
1175                     LOG(t);
1176                     setError(t);
1177                     if(t instanceof RestException) {
1178                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
1179                     }
1180                     else {
1181                         displayError("System error unable to copy file: " + t.getMessage());
1182                     }
1183                 }
1184
1185                 @Override
1186                 protected void onUnauthorized(Response response) {
1187                     sessionExpired();
1188                 }
1189             };
1190             copyFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1191             copyFile.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(file.getUri()));
1192             if(!file.getOwnerID().equals(targetUsername)) {
1193                 copyFile.setHeader(Const.X_SOURCE_ACCOUNT, URL.encodePathSegment(file.getOwnerID()));
1194             }
1195             copyFile.setHeader(Const.CONTENT_TYPE, file.getContentType());
1196             Scheduler.get().scheduleDeferred(copyFile);
1197         }
1198         else if(callback != null) {
1199             callback.execute();
1200         }
1201     }
1202
1203     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, boolean move, final Command callback) {
1204         String path = targetUri + "?delimiter=/";
1205         PutRequest copyFolder = new PutRequest(getStorageAPIURL(), targetUsername, path) {
1206             @Override
1207             public void onSuccess(Resource result) {
1208                 if(callback != null) {
1209                     callback.execute();
1210                 }
1211             }
1212
1213             @Override
1214             public void onError(Throwable t) {
1215                 LOG(t);
1216                 setError(t);
1217                 if(t instanceof RestException) {
1218                     displayError("Unable to copy folder: " + ((RestException) t).getHttpStatusText());
1219                 }
1220                 else {
1221                     displayError("System error copying folder: " + t.getMessage());
1222                 }
1223             }
1224
1225             @Override
1226             protected void onUnauthorized(Response response) {
1227                 sessionExpired();
1228             }
1229         };
1230         copyFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1231         copyFolder.setHeader(Const.ACCEPT, "*/*");
1232         copyFolder.setHeader(Const.CONTENT_LENGTH, "0");
1233         copyFolder.setHeader(Const.CONTENT_TYPE, "application/directory");
1234         if(!f.getOwnerID().equals(targetUsername)) {
1235             copyFolder.setHeader(Const.X_SOURCE_ACCOUNT, f.getOwnerID());
1236         }
1237         if(move) {
1238             copyFolder.setHeader(Const.X_MOVE_FROM, URL.encodePathSegment(f.getUri()));
1239         }
1240         else {
1241             copyFolder.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(f.getUri()));
1242         }
1243         Scheduler.get().scheduleDeferred(copyFolder);
1244     }
1245
1246     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1247         selectionModels.add(model);
1248     }
1249
1250     public OtherSharedTreeView getOtherSharedTreeView() {
1251         return otherSharedTreeView;
1252     }
1253
1254     public void updateTrash(boolean showFiles, Command callback) {
1255         updateFolder(trash, showFiles, callback, true);
1256     }
1257
1258     public void updateGroupsNode() {
1259         groupTreeView.updateGroupNode(null);
1260     }
1261
1262     public Group addGroup(String groupname) {
1263         Group newGroup = new Group(groupname);
1264         account.addGroup(newGroup);
1265         groupTreeView.updateGroupNode(null);
1266         return newGroup;
1267     }
1268
1269     public void removeGroup(Group group) {
1270         account.removeGroup(group);
1271         updateGroupsNode();
1272     }
1273
1274     public TreeView getSelectedTree() {
1275         return selectedTree;
1276     }
1277
1278     public void setSelectedTree(TreeView selected) {
1279         selectedTree = selected;
1280     }
1281
1282     public Folder getSelection() {
1283         if(selectedTree != null) {
1284             return selectedTree.getSelection();
1285         }
1286         return null;
1287     }
1288
1289     public void showFolderStatistics(int folderFileCount) {
1290         numOfFiles.setHTML(String.valueOf(folderFileCount));
1291     }
1292
1293     public GroupTreeView getGroupTreeView() {
1294         return groupTreeView;
1295     }
1296
1297     public void sessionExpired() {
1298         new SessionExpiredDialog(this).center();
1299     }
1300
1301     public void updateRootFolder(Command callback) {
1302         updateFolder(account.getPithos(), false, callback, true);
1303     }
1304
1305     void createMySharedTree() {
1306         LOG("Pithos::createMySharedTree()");
1307         mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1308         mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1309             @Override
1310             public void onSelectionChange(SelectionChangeEvent event) {
1311                 if(mysharedTreeSelectionModel.getSelectedObject() != null) {
1312                     deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
1313                     upload.setEnabled(false);
1314                     disableUploadArea();
1315                     updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
1316                     showRelevantToolbarButtons();
1317                 }
1318                 else {
1319                     if(getSelectedTree().equals(mysharedTreeView)) {
1320                         setSelectedTree(null);
1321                     }
1322                     if(getSelectedTree() == null) {
1323                         showRelevantToolbarButtons();
1324                     }
1325                 }
1326             }
1327         });
1328         selectionModels.add(mysharedTreeSelectionModel);
1329         mysharedTreeViewModel = new MysharedTreeViewModel(Pithos.this, mysharedTreeSelectionModel);
1330         mysharedTreeViewModel.initialize(new Command() {
1331
1332             @Override
1333             public void execute() {
1334                 mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
1335                 trees.insert(mysharedTreeView, 2);
1336                 treeViews.add(mysharedTreeView);
1337                 createOtherSharedTree();
1338             }
1339         });
1340     }
1341
1342     void createOtherSharedTree() {
1343         LOG("Pithos::createOtherSharedTree()");
1344         otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1345         otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1346             @Override
1347             public void onSelectionChange(SelectionChangeEvent event) {
1348                 if(otherSharedTreeSelectionModel.getSelectedObject() != null) {
1349                     deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
1350                     applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
1351                     updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true, null);
1352                     showRelevantToolbarButtons();
1353                 }
1354                 else {
1355                     if(getSelectedTree().equals(otherSharedTreeView)) {
1356                         setSelectedTree(null);
1357                     }
1358                     if(getSelectedTree() == null) {
1359                         showRelevantToolbarButtons();
1360                     }
1361                 }
1362             }
1363         });
1364         selectionModels.add(otherSharedTreeSelectionModel);
1365         otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
1366         // #3784 We show it empty...
1367         otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel, true);
1368         trees.insert(otherSharedTreeView, 1);
1369
1370         LOG("Pithos::createOtherSharedTree(), initializing otherSharedTreeViewModel with a callback");
1371         otherSharedTreeViewModel.initialize(new Command() {
1372             @Override
1373             public void execute() {
1374                 // #3784 ... then remove the empty stuff and add a new view with the populated model
1375                 trees.remove(otherSharedTreeView);
1376
1377                 otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel, false);
1378                 trees.insert(otherSharedTreeView, 1);
1379                 treeViews.add(otherSharedTreeView);
1380                 scheduleResfresh();
1381             }
1382         });
1383     }
1384
1385     public String getErrorData() {
1386         final StringBuilder sb = new StringBuilder();
1387         final String NL = Const.NL;
1388         Throwable t = this.error;
1389         while(t != null) {
1390             sb.append(t.toString());
1391             sb.append(NL);
1392             StackTraceElement[] traces = t.getStackTrace();
1393             for(StackTraceElement trace : traces) {
1394                 sb.append("  [");
1395                 sb.append(trace.getClassName());
1396                 sb.append("::");
1397                 sb.append(trace.getMethodName());
1398                 sb.append("() at ");
1399                 sb.append(trace.getFileName());
1400                 sb.append(":");
1401                 sb.append(trace.getLineNumber());
1402                 sb.append("]");
1403                 sb.append(NL);
1404             }
1405             t = t.getCause();
1406         }
1407
1408         return sb.toString();
1409     }
1410
1411     public void setError(Throwable t) {
1412         error = t;
1413         LOG(t);
1414     }
1415
1416     public void showRelevantToolbarButtons() {
1417         toolbar.showRelevantButtons();
1418     }
1419
1420     public FileUploadDialog getFileUploadDialog() {
1421         if(fileUploadDialog == null) {
1422             fileUploadDialog = new FileUploadDialog(this);
1423         }
1424         return fileUploadDialog;
1425     }
1426
1427     public void hideUploadIndicator() {
1428         upload.removeStyleName("pithos-uploadButton-loading");
1429         upload.setTitle("");
1430     }
1431
1432     public void showUploadIndicator() {
1433         upload.addStyleName("pithos-uploadButton-loading");
1434         upload.setTitle("Upload in progress. Click for details.");
1435     }
1436
1437     public void scheduleFolderHeadCommand(final Folder folder, final Command callback) {
1438         if(folder == null) {
1439             if(callback != null) {
1440                 callback.execute();
1441             }
1442         }
1443         else {
1444             HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getStorageAPIURL(), folder.getOwnerID(), folder.getUri(), folder) {
1445
1446                 @Override
1447                 public void onSuccess(Folder _result) {
1448                     if(callback != null) {
1449                         callback.execute();
1450                     }
1451                 }
1452
1453                 @Override
1454                 public void onError(Throwable t) {
1455                     if(t instanceof RestException) {
1456                         if(((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
1457                             final String path = folder.getUri();
1458                             PutRequest newFolder = new PutRequest(getStorageAPIURL(), folder.getOwnerID(), path) {
1459                                 @Override
1460                                 public void onSuccess(Resource _result) {
1461                                     scheduleFolderHeadCommand(folder, callback);
1462                                 }
1463
1464                                 @Override
1465                                 public void onError(Throwable _t) {
1466                                     setError(_t);
1467                                     if(_t instanceof RestException) {
1468                                         displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
1469                                     }
1470                                     else {
1471                                         displayError("System error creating folder: " + _t.getMessage());
1472                                     }
1473                                 }
1474
1475                                 @Override
1476                                 protected void onUnauthorized(Response response) {
1477                                     sessionExpired();
1478                                 }
1479                             };
1480                             newFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1481                             newFolder.setHeader(Const.CONTENT_TYPE, "application/folder");
1482                             newFolder.setHeader(Const.ACCEPT, "*/*");
1483                             newFolder.setHeader(Const.CONTENT_LENGTH, "0");
1484                             Scheduler.get().scheduleDeferred(newFolder);
1485                         }
1486                         else if(((RestException) t).getHttpStatusCode() == Response.SC_FORBIDDEN) {
1487                             onSuccess(folder);
1488                         }
1489                         else {
1490                             displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
1491                         }
1492                     }
1493                     else {
1494                         displayError("System error heading folder: " + t.getMessage());
1495                     }
1496
1497                     LOG("Error heading folder", t);
1498                     setError(t);
1499                 }
1500
1501                 @Override
1502                 protected void onUnauthorized(Response response) {
1503                     sessionExpired();
1504                 }
1505             };
1506             headFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1507             Scheduler.get().scheduleDeferred(headFolder);
1508         }
1509     }
1510
1511     public void scheduleFileHeadCommand(File f, final Command callback) {
1512         HeadRequest<File> headFile = new HeadRequest<File>(File.class, getStorageAPIURL(), f.getOwnerID(), f.getUri(), f) {
1513
1514             @Override
1515             public void onSuccess(File _result) {
1516                 if(callback != null) {
1517                     callback.execute();
1518                 }
1519             }
1520
1521             @Override
1522             public void onError(Throwable t) {
1523                 LOG("Error heading file", t);
1524                 setError(t);
1525                 if(t instanceof RestException) {
1526                     displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
1527                 }
1528                 else {
1529                     displayError("System error heading file: " + t.getMessage());
1530                 }
1531             }
1532
1533             @Override
1534             protected void onUnauthorized(Response response) {
1535                 sessionExpired();
1536             }
1537         };
1538         headFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1539         Scheduler.get().scheduleDeferred(headFile);
1540     }
1541
1542     public boolean isMySharedSelected() {
1543         return getSelectedTree().equals(getMySharedTreeView());
1544     }
1545
1546     private Folder getUploadFolder() {
1547         if(folderTreeView.equals(getSelectedTree()) || otherSharedTreeView.equals(getSelectedTree())) {
1548             return getSelection();
1549         }
1550         return null;
1551     }
1552
1553     private void updateUploadFolder() {
1554         updateUploadFolder(null);
1555     }
1556
1557     private void updateUploadFolder(final JsArrayString urls) {
1558         if(folderTreeView.equals(getSelectedTree()) || otherSharedTreeView.equals(getSelectedTree())) {
1559             Folder f = getSelection();
1560             if(getSelectedTree().equals(getFolderTreeView())) {
1561                 updateFolder(f, true, new Command() {
1562
1563                     @Override
1564                     public void execute() {
1565                         updateStatistics();
1566                         if(urls != null) {
1567                             selectUploadedFiles(urls);
1568                         }
1569                     }
1570                 }, false);
1571             }
1572             else {
1573                 updateOtherSharedFolder(f, true, null);
1574             }
1575         }
1576     }
1577
1578     public native void disableUploadArea() /*-{
1579       var uploader = $wnd.$("#uploader").pluploadQueue();
1580       var dropElm = $wnd.document.getElementById('rightPanel');
1581       $wnd.plupload.removeAllEvents(dropElm, uploader.id);
1582     }-*/;
1583
1584     public native void enableUploadArea() /*-{
1585       var uploader = $wnd.$("#uploader").pluploadQueue();
1586       var dropElm = $wnd.document.getElementById('rightPanel');
1587       $wnd.plupload.removeAllEvents(dropElm, uploader.id);
1588       if (uploader.runtime == 'html5') {
1589         uploader.settings.drop_element = 'rightPanel';
1590         uploader.trigger('PostInit');
1591       }
1592     }-*/;
1593
1594     public void showUploadAlert(int nOfFiles) {
1595         if(uploadAlert == null) {
1596             uploadAlert = new UploadAlert(this, nOfFiles);
1597         }
1598         if(!uploadAlert.isShowing()) {
1599             uploadAlert.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
1600
1601                 @Override
1602                 public void setPosition(int offsetWidth, int offsetHeight) {
1603                     uploadAlert.setPopupPosition((Window.getClientWidth() - offsetWidth) / 2, statusPanel.getAbsoluteTop() - offsetHeight);
1604                 }
1605             });
1606         }
1607         uploadAlert.setNumOfFiles(nOfFiles);
1608     }
1609
1610     public void hideUploadAlert() {
1611         if(uploadAlert != null && uploadAlert.isShowing()) {
1612             uploadAlert.hide();
1613         }
1614     }
1615
1616     public void selectUploadedFiles(JsArrayString urls) {
1617         List<String> selectedUrls = new ArrayList<String>();
1618         for(int i = 0; i < urls.length(); i++) {
1619             selectedUrls.add(urls.get(i));
1620         }
1621         fileList.selectByUrl(selectedUrls);
1622     }
1623
1624     public void purgeContainer(final Folder container) {
1625         String path = "/" + container.getName() + "?delimiter=/";
1626         DeleteRequest delete = new DeleteRequest(getStorageAPIURL(), getUserID(), path) {
1627
1628             @Override
1629             protected void onUnauthorized(Response response) {
1630                 sessionExpired();
1631             }
1632
1633             @Override
1634             public void onSuccess(Resource result) {
1635                 updateFolder(container, true, null, true);
1636             }
1637
1638             @Override
1639             public void onError(Throwable t) {
1640                 LOG("Error deleting trash", t);
1641                 setError(t);
1642                 if(t instanceof RestException) {
1643                     displayError("Error deleting trash: " + ((RestException) t).getHttpStatusText());
1644                 }
1645                 else {
1646                     displayError("System error deleting trash: " + t.getMessage());
1647                 }
1648             }
1649         };
1650         delete.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1651         Scheduler.get().scheduleDeferred(delete);
1652     }
1653 }