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