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