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